grammatik 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -0
- package/bin/grammatik.js +8 -0
- package/dist/cjs/index.js +1637 -139
- package/dist/cjs/runtime.js +642 -34
- package/dist/esm/index.js +1637 -139
- package/dist/esm/runtime.js +642 -34
- package/dist/types/index.d.ts +36 -0
- package/dist/types/runtime.d.ts +4 -3
- package/package.json +3 -2
- package/src/analyze.js +225 -18
- package/src/codegen.js +218 -27
- package/src/definition.js +5 -0
- package/src/emit.js +41 -7
- package/src/lexer.d.ts +43 -2
- package/src/lexer.js +573 -44
- package/src/lexgen.js +451 -0
- package/src/memoplan.js +220 -0
- package/src/methods.js +1 -1
- package/src/parser.js +38 -3
- package/src/plan.js +142 -30
- package/src/runtime.js +8 -6
- package/src/scan.js +422 -0
- package/src/state.js +33 -1
package/dist/esm/index.js
CHANGED
|
@@ -315,7 +315,338 @@ function firstCharSet(source, ignoreCase) {
|
|
|
315
315
|
return found;
|
|
316
316
|
}
|
|
317
317
|
|
|
318
|
-
// src/
|
|
318
|
+
// src/scan.js
|
|
319
|
+
var DEFER = -2;
|
|
320
|
+
var CLASS = 0;
|
|
321
|
+
var SEQ2 = 1;
|
|
322
|
+
var KEYWORD = 2;
|
|
323
|
+
var LINE = 3;
|
|
324
|
+
var QUOTED = 4;
|
|
325
|
+
var NUMBER = 5;
|
|
326
|
+
var STRING = 6;
|
|
327
|
+
var SCANNER_KINDS = Object.freeze({ CLASS, SEQ2, KEYWORD, LINE, QUOTED, NUMBER, STRING });
|
|
328
|
+
var NONE = new Uint8Array(128);
|
|
329
|
+
function make(kind, fields) {
|
|
330
|
+
return {
|
|
331
|
+
kind,
|
|
332
|
+
wide: false,
|
|
333
|
+
first: NONE,
|
|
334
|
+
rest: NONE,
|
|
335
|
+
second: NONE,
|
|
336
|
+
look: NONE,
|
|
337
|
+
lower: [],
|
|
338
|
+
upper: [],
|
|
339
|
+
prefix: "",
|
|
340
|
+
quote: -1,
|
|
341
|
+
sign: false,
|
|
342
|
+
jsonInt: false,
|
|
343
|
+
frac: false,
|
|
344
|
+
exp: false,
|
|
345
|
+
ctrl: false,
|
|
346
|
+
uni: false,
|
|
347
|
+
escAny: false,
|
|
348
|
+
esc: NONE,
|
|
349
|
+
...fields
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
var SHORTHAND = {
|
|
353
|
+
d: (t) => {
|
|
354
|
+
for (let k = 48; k < 58; k++) t[k] = 1;
|
|
355
|
+
},
|
|
356
|
+
w: (t) => {
|
|
357
|
+
for (let k = 0; k < 128; k++) if (/\w/.test(String.fromCharCode(k))) t[k] = 1;
|
|
358
|
+
},
|
|
359
|
+
s: (t) => {
|
|
360
|
+
for (const k of [9, 10, 11, 12, 13, 32]) t[k] = 1;
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
var SIMPLE_ESCAPES2 = { t: " ", r: "\r", n: "\n", f: "\f", v: "\v" };
|
|
364
|
+
var ALNUM = /[a-zA-Z0-9]/;
|
|
365
|
+
function parseClass(body) {
|
|
366
|
+
if (body.length === 0 || body[0] === "^") return null;
|
|
367
|
+
const table = new Uint8Array(128);
|
|
368
|
+
for (let i = 0; i < body.length; i++) {
|
|
369
|
+
let c = body[i];
|
|
370
|
+
if (c === "\\") {
|
|
371
|
+
const n = body[++i];
|
|
372
|
+
if (n === "d" || n === "w") {
|
|
373
|
+
SHORTHAND[n](table);
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
if (n === void 0) return null;
|
|
377
|
+
if (ALNUM.test(n) && !(n in SIMPLE_ESCAPES2)) return null;
|
|
378
|
+
c = SIMPLE_ESCAPES2[n] ?? n;
|
|
379
|
+
}
|
|
380
|
+
if (body[i + 1] === "-" && i + 2 < body.length) {
|
|
381
|
+
let hi = body[i + 2];
|
|
382
|
+
let advance = 2;
|
|
383
|
+
if (hi === "\\") {
|
|
384
|
+
hi = body[i + 3];
|
|
385
|
+
advance = 3;
|
|
386
|
+
if (hi === void 0 || ALNUM.test(hi)) return null;
|
|
387
|
+
}
|
|
388
|
+
const from = c.charCodeAt(0);
|
|
389
|
+
const to = hi.charCodeAt(0);
|
|
390
|
+
if (from > to || to >= 128) return null;
|
|
391
|
+
for (let k = from; k <= to; k++) table[k] = 1;
|
|
392
|
+
i += advance;
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
const code = c.charCodeAt(0);
|
|
396
|
+
if (code >= 128) return null;
|
|
397
|
+
table[code] = 1;
|
|
398
|
+
}
|
|
399
|
+
return table;
|
|
400
|
+
}
|
|
401
|
+
var BODY = String.raw`((?:[^\]\\]|\\.)*)`;
|
|
402
|
+
var RE_CLASS_RUN = new RegExp(String.raw`^\[${BODY}\]([+*])$`);
|
|
403
|
+
var RE_CLASS_PAIR = new RegExp(String.raw`^\[${BODY}\]\[${BODY}\]\*$`);
|
|
404
|
+
var RE_CLASS_SEQ = new RegExp(String.raw`^\[${BODY}\]\+\[${BODY}\]\+$`);
|
|
405
|
+
var RE_SHORTHAND = /^\\([sdw])[+*]$/;
|
|
406
|
+
var RE_KEYWORD = new RegExp(String.raw`^([A-Za-z0-9_]+)\(\?!\[${BODY}\]\)$`);
|
|
407
|
+
var RE_LINE = new RegExp(String.raw`^([^\\\[\]()*+?.|^$]+)\[\^${BODY}\]\*$`);
|
|
408
|
+
var RE_QUOTED = /^(['"])\(\?:\1\1\|\[\^\1\]\)\*\1$/;
|
|
409
|
+
var RE_NUMBER = /^(-\?)?(?:(\(\?:0\|\[1-9\]\\d\*\))|\\d\+)(\(\?:\\\.\\d\+\)\?)?(\(\?:\[eE\]\[\+-\]\?\\d\+\)\?)?$/;
|
|
410
|
+
var RE_STRING_ESC = new RegExp(String.raw`^(["'])\(\?:\[\^\1\\\\(\\u0000-\\u001f|\\x00-\\x1f)?\]\|\\\\\(\?:\[${BODY}\](\|u\[0-9a-fA-F\]\{4\})?\)\)\*\1$`);
|
|
411
|
+
var RE_STRING_ANY = new RegExp(String.raw`^(["'])\(\?:\[\^\1\\\\\]\|\\\\\.\)\*\1$`);
|
|
412
|
+
function buildScanner(source, ignoreCase) {
|
|
413
|
+
let m = RE_KEYWORD.exec(source);
|
|
414
|
+
if (m) {
|
|
415
|
+
const look = parseClass(m[2]);
|
|
416
|
+
if (look === null) return null;
|
|
417
|
+
const word = m[1];
|
|
418
|
+
const lower = [];
|
|
419
|
+
const upper = [];
|
|
420
|
+
for (const ch of word) {
|
|
421
|
+
lower.push((ignoreCase ? ch.toLowerCase() : ch).charCodeAt(0));
|
|
422
|
+
upper.push((ignoreCase ? ch.toUpperCase() : ch).charCodeAt(0));
|
|
423
|
+
}
|
|
424
|
+
return make(KEYWORD, { look, lower, upper });
|
|
425
|
+
}
|
|
426
|
+
if (ignoreCase) return null;
|
|
427
|
+
m = RE_STRING_ESC.exec(source);
|
|
428
|
+
if (m) {
|
|
429
|
+
const esc = parseClass(m[3]);
|
|
430
|
+
if (esc === null) return null;
|
|
431
|
+
return make(STRING, { quote: m[1].charCodeAt(0), ctrl: m[2] !== void 0, uni: m[4] !== void 0, esc });
|
|
432
|
+
}
|
|
433
|
+
m = RE_STRING_ANY.exec(source);
|
|
434
|
+
if (m) return make(STRING, { quote: m[1].charCodeAt(0), escAny: true });
|
|
435
|
+
m = RE_NUMBER.exec(source.replace(/\[0-9\]/g, "\\d"));
|
|
436
|
+
if (m && (m[1] || m[2] || m[3] || m[4])) {
|
|
437
|
+
return make(NUMBER, { sign: m[1] !== void 0, jsonInt: m[2] !== void 0, frac: m[3] !== void 0, exp: m[4] !== void 0 });
|
|
438
|
+
}
|
|
439
|
+
m = RE_CLASS_RUN.exec(source);
|
|
440
|
+
if (m) {
|
|
441
|
+
const table = parseClass(m[1]);
|
|
442
|
+
return table && make(CLASS, { first: table, rest: table });
|
|
443
|
+
}
|
|
444
|
+
m = RE_CLASS_PAIR.exec(source);
|
|
445
|
+
if (m) {
|
|
446
|
+
const first = parseClass(m[1]);
|
|
447
|
+
const rest = parseClass(m[2]);
|
|
448
|
+
return first && rest && make(CLASS, { first, rest });
|
|
449
|
+
}
|
|
450
|
+
m = RE_CLASS_SEQ.exec(source);
|
|
451
|
+
if (m) {
|
|
452
|
+
const first = parseClass(m[1]);
|
|
453
|
+
const second = parseClass(m[2]);
|
|
454
|
+
if (first && second && first.every((v, i) => !(v && second[i]))) {
|
|
455
|
+
return make(SEQ2, { first, rest: first, second });
|
|
456
|
+
}
|
|
457
|
+
return null;
|
|
458
|
+
}
|
|
459
|
+
m = RE_SHORTHAND.exec(source);
|
|
460
|
+
if (m) {
|
|
461
|
+
const table = new Uint8Array(128);
|
|
462
|
+
SHORTHAND[m[1]](table);
|
|
463
|
+
return make(CLASS, { first: table, rest: table, wide: m[1] === "s" });
|
|
464
|
+
}
|
|
465
|
+
m = RE_QUOTED.exec(source);
|
|
466
|
+
if (m) return make(QUOTED, { quote: m[1].charCodeAt(0) });
|
|
467
|
+
m = RE_LINE.exec(source);
|
|
468
|
+
if (m) {
|
|
469
|
+
const stop = parseClass(m[2]);
|
|
470
|
+
return stop && make(LINE, { prefix: m[1], look: stop });
|
|
471
|
+
}
|
|
472
|
+
return null;
|
|
473
|
+
}
|
|
474
|
+
var skipDigits = (text, i, length) => {
|
|
475
|
+
while (i < length) {
|
|
476
|
+
const c = text.charCodeAt(i);
|
|
477
|
+
if (c < 48 || c > 57) break;
|
|
478
|
+
i++;
|
|
479
|
+
}
|
|
480
|
+
return i;
|
|
481
|
+
};
|
|
482
|
+
var isHex = (c) => c >= 48 && c <= 57 || c >= 65 && c <= 70 || c >= 97 && c <= 102;
|
|
483
|
+
function scanEnd(sc, text, offset, length) {
|
|
484
|
+
switch (sc.kind) {
|
|
485
|
+
case CLASS: {
|
|
486
|
+
const code = text.charCodeAt(offset);
|
|
487
|
+
if (code >= 128) return sc.wide ? DEFER : -1;
|
|
488
|
+
if (sc.first[code] !== 1) return -1;
|
|
489
|
+
const rest = sc.rest;
|
|
490
|
+
let j = offset + 1;
|
|
491
|
+
while (j < length) {
|
|
492
|
+
const c = text.charCodeAt(j);
|
|
493
|
+
if (c >= 128) return sc.wide ? DEFER : j;
|
|
494
|
+
if (rest[c] !== 1) break;
|
|
495
|
+
j++;
|
|
496
|
+
}
|
|
497
|
+
return j;
|
|
498
|
+
}
|
|
499
|
+
case SEQ2: {
|
|
500
|
+
const c0 = text.charCodeAt(offset);
|
|
501
|
+
if (c0 >= 128 || sc.first[c0] !== 1) return -1;
|
|
502
|
+
const { first, second } = sc;
|
|
503
|
+
let j = offset + 1;
|
|
504
|
+
while (j < length) {
|
|
505
|
+
const c = text.charCodeAt(j);
|
|
506
|
+
if (c < 128 && first[c] === 1) j++;
|
|
507
|
+
else break;
|
|
508
|
+
}
|
|
509
|
+
const mid = j;
|
|
510
|
+
while (j < length) {
|
|
511
|
+
const c = text.charCodeAt(j);
|
|
512
|
+
if (c < 128 && second[c] === 1) j++;
|
|
513
|
+
else break;
|
|
514
|
+
}
|
|
515
|
+
return j > mid ? j : -1;
|
|
516
|
+
}
|
|
517
|
+
case KEYWORD: {
|
|
518
|
+
const { lower, upper } = sc;
|
|
519
|
+
const n = lower.length;
|
|
520
|
+
if (offset + n > length) return -1;
|
|
521
|
+
for (let i = 0; i < n; i++) {
|
|
522
|
+
const c = text.charCodeAt(offset + i);
|
|
523
|
+
if (c !== lower[i] && c !== upper[i]) return -1;
|
|
524
|
+
}
|
|
525
|
+
const end = offset + n;
|
|
526
|
+
if (end < length) {
|
|
527
|
+
const c = text.charCodeAt(end);
|
|
528
|
+
if (c < 128 && sc.look[c] === 1) return -1;
|
|
529
|
+
}
|
|
530
|
+
return end;
|
|
531
|
+
}
|
|
532
|
+
case LINE: {
|
|
533
|
+
if (!text.startsWith(sc.prefix, offset)) return -1;
|
|
534
|
+
let j = offset + sc.prefix.length;
|
|
535
|
+
const stop = sc.look;
|
|
536
|
+
while (j < length) {
|
|
537
|
+
const c = text.charCodeAt(j);
|
|
538
|
+
if (c < 128 && stop[c] === 1) break;
|
|
539
|
+
j++;
|
|
540
|
+
}
|
|
541
|
+
return j;
|
|
542
|
+
}
|
|
543
|
+
case NUMBER: {
|
|
544
|
+
let j = offset;
|
|
545
|
+
if (sc.sign && text.charCodeAt(j) === 45) j++;
|
|
546
|
+
const c = text.charCodeAt(j);
|
|
547
|
+
if (sc.jsonInt) {
|
|
548
|
+
if (c === 48) j++;
|
|
549
|
+
else if (c >= 49 && c <= 57) j = skipDigits(text, j + 1, length);
|
|
550
|
+
else return -1;
|
|
551
|
+
} else {
|
|
552
|
+
if (!(c >= 48 && c <= 57)) return -1;
|
|
553
|
+
j = skipDigits(text, j + 1, length);
|
|
554
|
+
}
|
|
555
|
+
if (sc.frac && text.charCodeAt(j) === 46) {
|
|
556
|
+
const k = skipDigits(text, j + 1, length);
|
|
557
|
+
if (k > j + 1) j = k;
|
|
558
|
+
}
|
|
559
|
+
if (sc.exp) {
|
|
560
|
+
const e = text.charCodeAt(j);
|
|
561
|
+
if (e === 101 || e === 69) {
|
|
562
|
+
let k = j + 1;
|
|
563
|
+
const s = text.charCodeAt(k);
|
|
564
|
+
if (s === 43 || s === 45) k++;
|
|
565
|
+
const end = skipDigits(text, k, length);
|
|
566
|
+
if (end > k) j = end;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
return j;
|
|
570
|
+
}
|
|
571
|
+
case STRING: {
|
|
572
|
+
const q2 = sc.quote;
|
|
573
|
+
if (text.charCodeAt(offset) !== q2) return -1;
|
|
574
|
+
let j = offset + 1;
|
|
575
|
+
for (; ; ) {
|
|
576
|
+
if (j >= length) return -1;
|
|
577
|
+
const c = text.charCodeAt(j);
|
|
578
|
+
if (c === q2) return j + 1;
|
|
579
|
+
if (c === 92) {
|
|
580
|
+
const d = text.charCodeAt(j + 1);
|
|
581
|
+
if (sc.escAny) {
|
|
582
|
+
if (!(d >= 0) || d === 10 || d === 13 || d === 8232 || d === 8233) return -1;
|
|
583
|
+
j += 2;
|
|
584
|
+
} else if (d < 128 && sc.esc[d] === 1) {
|
|
585
|
+
j += 2;
|
|
586
|
+
} 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))) {
|
|
587
|
+
j += 6;
|
|
588
|
+
} else {
|
|
589
|
+
return -1;
|
|
590
|
+
}
|
|
591
|
+
continue;
|
|
592
|
+
}
|
|
593
|
+
if (sc.ctrl && c < 32) return -1;
|
|
594
|
+
j++;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
default: {
|
|
598
|
+
const q2 = sc.quote;
|
|
599
|
+
if (text.charCodeAt(offset) !== q2) return -1;
|
|
600
|
+
const quote2 = String.fromCharCode(q2);
|
|
601
|
+
let fallback = -1;
|
|
602
|
+
let j = offset + 1;
|
|
603
|
+
for (; ; ) {
|
|
604
|
+
j = text.indexOf(quote2, j);
|
|
605
|
+
if (j === -1) return fallback;
|
|
606
|
+
if (text.charCodeAt(j + 1) !== q2) return j + 1;
|
|
607
|
+
fallback = j + 1;
|
|
608
|
+
j += 2;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// src/state.js
|
|
615
|
+
var FAIL = /* @__PURE__ */ Symbol("grammatik.fail");
|
|
616
|
+
var memoTables = (slots, length) => {
|
|
617
|
+
if (!slots) return null;
|
|
618
|
+
const tables = new Array(slots);
|
|
619
|
+
for (let i = 0; i < slots; i++) tables[i] = { ends: new Int32Array(length + 1), vals: new Array(length + 1) };
|
|
620
|
+
return tables;
|
|
621
|
+
};
|
|
622
|
+
var createState = (tokens, grammar, options = {}) => ({
|
|
623
|
+
tokens,
|
|
624
|
+
idx: 0,
|
|
625
|
+
maxIdx: 0,
|
|
626
|
+
expected: [],
|
|
627
|
+
grammar,
|
|
628
|
+
memo: options.memo ? /* @__PURE__ */ new Map() : null,
|
|
629
|
+
rm: memoTables(options.memoSlots, tokens.length),
|
|
630
|
+
// Entering a rule is one array store rather than an object allocation, and
|
|
631
|
+
// the stack is snapshotted only on a furthest failure — by the time an error
|
|
632
|
+
// is built it would otherwise have unwound to just the start rule.
|
|
633
|
+
ruleNames: [],
|
|
634
|
+
ruleDepth: 0,
|
|
635
|
+
failureStack: null,
|
|
636
|
+
track: options.track === true,
|
|
637
|
+
steps: 0,
|
|
638
|
+
maxSteps: options.maxSteps || 0
|
|
639
|
+
});
|
|
640
|
+
function expect(state, expectation) {
|
|
641
|
+
if (state.idx > state.maxIdx) {
|
|
642
|
+
state.maxIdx = state.idx;
|
|
643
|
+
if (!state.track) return;
|
|
644
|
+
state.expected = [];
|
|
645
|
+
state.failureStack = state.ruleNames.slice(0, state.ruleDepth);
|
|
646
|
+
}
|
|
647
|
+
if (!state.track || state.idx !== state.maxIdx) return;
|
|
648
|
+
if (!state.expected.includes(expectation)) state.expected.push(expectation);
|
|
649
|
+
}
|
|
319
650
|
var LexError = class extends Error {
|
|
320
651
|
/**
|
|
321
652
|
* @param {string} message
|
|
@@ -329,6 +660,33 @@ var LexError = class extends Error {
|
|
|
329
660
|
this.col = position.col;
|
|
330
661
|
}
|
|
331
662
|
};
|
|
663
|
+
var ParseError = class extends Error {
|
|
664
|
+
/**
|
|
665
|
+
* @param {string} message
|
|
666
|
+
* @param {{ token: import('./lexer.js').Token | null, expected: string[], ruleStack: string[] }} details
|
|
667
|
+
*/
|
|
668
|
+
constructor(message, details) {
|
|
669
|
+
super(message);
|
|
670
|
+
this.name = "ParseError";
|
|
671
|
+
this.token = details.token;
|
|
672
|
+
this.expected = details.expected;
|
|
673
|
+
this.ruleStack = details.ruleStack;
|
|
674
|
+
}
|
|
675
|
+
};
|
|
676
|
+
var describeExpected = (expected) => expected.length === 0 ? "nothing" : expected.length === 1 ? expected[0] : `one of [${expected.join(", ")}]`;
|
|
677
|
+
function buildParseError(state) {
|
|
678
|
+
const token = state.tokens[state.maxIdx] || null;
|
|
679
|
+
const expected = [...state.expected].sort();
|
|
680
|
+
const ruleStack = state.failureStack || state.ruleNames.slice(0, state.ruleDepth);
|
|
681
|
+
const at = (t) => state.positions === "offset" ? `offset ${t.start}` : `line ${t.line} column ${t.col}`;
|
|
682
|
+
const found = token ? `--> ${token.image} <-- (${token.type}) at ${at(token)}` : "end of input";
|
|
683
|
+
return new ParseError(
|
|
684
|
+
`Expecting ${describeExpected(expected)} but found ${found}`,
|
|
685
|
+
{ token, expected, ruleStack }
|
|
686
|
+
);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
// src/lexer.js
|
|
332
690
|
var DEFAULT_MODE = "default";
|
|
333
691
|
function modesOf(def) {
|
|
334
692
|
if (Array.isArray(def.modes) && def.modes.length > 0) return def.modes;
|
|
@@ -338,6 +696,8 @@ function modesOf(def) {
|
|
|
338
696
|
function createLexer(defs, options = {}) {
|
|
339
697
|
const positions = options.positions === "offset" ? "offset" : "full";
|
|
340
698
|
const trackLines = positions === "full";
|
|
699
|
+
const useScanners = options.scanners !== false;
|
|
700
|
+
const useFuse = options.fuse !== false;
|
|
341
701
|
if (!Array.isArray(defs) || defs.length === 0) throw new Error("createLexer: expected a non-empty array of token definitions");
|
|
342
702
|
const tokenNames = defs.map((d) => d.name);
|
|
343
703
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -357,7 +717,7 @@ function createLexer(defs, options = {}) {
|
|
|
357
717
|
return id;
|
|
358
718
|
};
|
|
359
719
|
for (const def of defs) for (const mode of modesOf(def)) idOf(mode);
|
|
360
|
-
const compiled = defs.map((def) => {
|
|
720
|
+
const compiled = defs.map((def, id) => {
|
|
361
721
|
if (!def.name) throw new Error("createLexer: every token definition needs a name");
|
|
362
722
|
if (!def.pattern && !def.literal) throw new Error(`createLexer: token '${def.name}' needs a pattern or a literal`);
|
|
363
723
|
if (def.pattern && def.literal) throw new Error(`createLexer: token '${def.name}' has both a pattern and a literal; pick one`);
|
|
@@ -372,13 +732,24 @@ function createLexer(defs, options = {}) {
|
|
|
372
732
|
}
|
|
373
733
|
return {
|
|
374
734
|
name: def.name,
|
|
735
|
+
// The declaration index. `createParser` derives the identical mapping
|
|
736
|
+
// from `tokenNames` independently (same array, same order), so a
|
|
737
|
+
// combinator that resolves a `consume` target's id at build time agrees
|
|
738
|
+
// with what a token actually carries without the two ever comparing notes.
|
|
739
|
+
id,
|
|
375
740
|
skip: !!def.skip,
|
|
376
741
|
literal: def.literal,
|
|
377
742
|
longerAlt: def.longerAlt || null,
|
|
378
743
|
regex: def.pattern ? new RegExp(def.pattern, def.ignoreCase ? "iy" : "y") : null,
|
|
744
|
+
// A table-driven stand-in for the regex where its shape is recognised;
|
|
745
|
+
// null otherwise (and always, with `scanners: false`). It is a field on
|
|
746
|
+
// every definition so they all keep one shape. See scan.js.
|
|
747
|
+
scanner: useScanners && def.pattern ? buildScanner(def.pattern, !!def.ignoreCase) : null,
|
|
379
748
|
modes: modesOf(def),
|
|
380
749
|
pushMode: def.pushMode ? idOf(def.pushMode) : -1,
|
|
381
|
-
popMode: !!def.popMode
|
|
750
|
+
popMode: !!def.popMode,
|
|
751
|
+
// Set only on the stand-in `fuse` builds for a run of regexes; see there.
|
|
752
|
+
members: null
|
|
382
753
|
};
|
|
383
754
|
});
|
|
384
755
|
const byName = new Map(compiled.map((c) => [c.name, c]));
|
|
@@ -395,14 +766,47 @@ function createLexer(defs, options = {}) {
|
|
|
395
766
|
for (const c of compiled) {
|
|
396
767
|
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);
|
|
397
768
|
}
|
|
398
|
-
const
|
|
769
|
+
const FUSE_MIN = 3;
|
|
770
|
+
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.
|
|
771
|
+
new RegExp(def.regex.source + "|").exec("").length === 1;
|
|
772
|
+
function fuse(list) {
|
|
773
|
+
if (!useFuse) return list;
|
|
774
|
+
const out = [];
|
|
775
|
+
for (let i = 0; i < list.length; ) {
|
|
776
|
+
let j = i;
|
|
777
|
+
while (j < list.length && fusible(list[j]) && list[j].regex.flags === list[i].regex.flags) j++;
|
|
778
|
+
if (j - i < FUSE_MIN) {
|
|
779
|
+
out.push(list[i++]);
|
|
780
|
+
continue;
|
|
781
|
+
}
|
|
782
|
+
const run = list.slice(i, j);
|
|
783
|
+
out.push({
|
|
784
|
+
name: run.map((d) => d.name).join("|"),
|
|
785
|
+
id: -1,
|
|
786
|
+
skip: false,
|
|
787
|
+
literal: void 0,
|
|
788
|
+
longerAlt: null,
|
|
789
|
+
longerAltDef: void 0,
|
|
790
|
+
regex: new RegExp(run.map((d) => `(${d.regex.source})`).join("|"), run[0].regex.flags),
|
|
791
|
+
scanner: null,
|
|
792
|
+
modes: run[0].modes,
|
|
793
|
+
pushMode: -1,
|
|
794
|
+
popMode: false,
|
|
795
|
+
members: run
|
|
796
|
+
});
|
|
797
|
+
i = j;
|
|
798
|
+
}
|
|
799
|
+
return out;
|
|
800
|
+
}
|
|
801
|
+
const modes = modeNames.map((name, id) => {
|
|
399
802
|
const members = compiled.filter((c) => c.modes.includes(name));
|
|
400
803
|
const alwaysTry = members.filter((c) => c.firstChars === null);
|
|
401
|
-
const candidatesFor = (code) => members.filter((c) => c.firstChars === null || c.firstChars.has(code));
|
|
804
|
+
const candidatesFor = (code) => fuse(members.filter((c) => c.firstChars === null || c.firstChars.has(code)));
|
|
402
805
|
const ascii = new Array(128);
|
|
403
806
|
for (let code = 0; code < 128; code++) ascii[code] = candidatesFor(code);
|
|
404
807
|
return {
|
|
405
808
|
name,
|
|
809
|
+
id,
|
|
406
810
|
members,
|
|
407
811
|
ascii,
|
|
408
812
|
wide: /* @__PURE__ */ new Map(),
|
|
@@ -426,6 +830,30 @@ function createLexer(defs, options = {}) {
|
|
|
426
830
|
const multiMode = modes.length > 1 || compiled.some((c) => c.popMode || c.pushMode !== -1);
|
|
427
831
|
const namesModes = modes.length > 1;
|
|
428
832
|
const initial = modes[0];
|
|
833
|
+
let fusedMember = null;
|
|
834
|
+
let fusedText = "";
|
|
835
|
+
function fusedEnd(run, text, offset) {
|
|
836
|
+
run.regex.lastIndex = offset;
|
|
837
|
+
const match = run.regex.exec(text);
|
|
838
|
+
if (match === null) return -1;
|
|
839
|
+
if (match[0].length === 0) {
|
|
840
|
+
for (const member of run.members) {
|
|
841
|
+
member.regex.lastIndex = offset;
|
|
842
|
+
const one = member.regex.exec(text);
|
|
843
|
+
if (one !== null && one[0].length > 0) {
|
|
844
|
+
fusedMember = member;
|
|
845
|
+
fusedText = one[0];
|
|
846
|
+
return offset + fusedText.length;
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
return -1;
|
|
850
|
+
}
|
|
851
|
+
let group = 1;
|
|
852
|
+
while (match[group] === void 0) group++;
|
|
853
|
+
fusedMember = run.members[group - 1];
|
|
854
|
+
fusedText = match[0];
|
|
855
|
+
return offset + fusedText.length;
|
|
856
|
+
}
|
|
429
857
|
function longerAltEnd(def, text, offset) {
|
|
430
858
|
const alt2 = def.longerAltDef;
|
|
431
859
|
if (alt2.literal !== void 0) return text.startsWith(alt2.literal, offset) ? offset + alt2.literal.length : -1;
|
|
@@ -445,19 +873,30 @@ function createLexer(defs, options = {}) {
|
|
|
445
873
|
outer: while (offset < length) {
|
|
446
874
|
const applicable = mode.dispatch ? candidates(mode, text.charCodeAt(offset)) : mode.members;
|
|
447
875
|
for (let i = 0; i < applicable.length; i++) {
|
|
448
|
-
|
|
876
|
+
let def = applicable[i];
|
|
449
877
|
let end = -1;
|
|
450
878
|
let matched = null;
|
|
451
|
-
if (def.
|
|
879
|
+
if (def.members !== null) {
|
|
880
|
+
end = fusedEnd(def, text, offset);
|
|
881
|
+
if (end === -1) continue;
|
|
882
|
+
def = fusedMember;
|
|
883
|
+
matched = fusedText;
|
|
884
|
+
} else if (def.literal !== void 0) {
|
|
452
885
|
if (text.startsWith(def.literal, offset)) end = offset + def.literal.length;
|
|
453
|
-
} else if (def.skip) {
|
|
454
|
-
def.regex.lastIndex = offset;
|
|
455
|
-
if (def.regex.test(text) && def.regex.lastIndex > offset) end = def.regex.lastIndex;
|
|
456
886
|
} else {
|
|
457
|
-
def.
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
887
|
+
end = def.scanner === null ? DEFER : scanEnd(def.scanner, text, offset, length);
|
|
888
|
+
if (end === DEFER) {
|
|
889
|
+
end = -1;
|
|
890
|
+
if (def.skip) {
|
|
891
|
+
def.regex.lastIndex = offset;
|
|
892
|
+
if (def.regex.test(text) && def.regex.lastIndex > offset) end = def.regex.lastIndex;
|
|
893
|
+
} else {
|
|
894
|
+
def.regex.lastIndex = offset;
|
|
895
|
+
const match = def.regex.exec(text);
|
|
896
|
+
if (match !== null && match[0].length > 0) matched = match[0];
|
|
897
|
+
if (matched !== null) end = offset + matched.length;
|
|
898
|
+
}
|
|
899
|
+
}
|
|
461
900
|
}
|
|
462
901
|
if (end === -1) continue;
|
|
463
902
|
let winner = def;
|
|
@@ -470,14 +909,7 @@ function createLexer(defs, options = {}) {
|
|
|
470
909
|
}
|
|
471
910
|
if (!winner.skip) {
|
|
472
911
|
const image = winner.literal !== void 0 ? winner.literal : winner === def && matched !== null ? matched : text.slice(offset, end);
|
|
473
|
-
tokens.push({
|
|
474
|
-
type: winner.name,
|
|
475
|
-
image,
|
|
476
|
-
start: offset,
|
|
477
|
-
end,
|
|
478
|
-
line: trackLines ? line : 0,
|
|
479
|
-
col: trackLines ? offset - lineStart + 1 : 0
|
|
480
|
-
});
|
|
912
|
+
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 });
|
|
481
913
|
}
|
|
482
914
|
if (trackLines) {
|
|
483
915
|
for (let j = offset; j < end; j++) {
|
|
@@ -513,62 +945,238 @@ function createLexer(defs, options = {}) {
|
|
|
513
945
|
}
|
|
514
946
|
const inMode = (mode) => namesModes ? ` (in lexer mode '${mode.name}')` : "";
|
|
515
947
|
const describe = (offset, line, lineStart) => trackLines ? `line ${line} column ${offset - lineStart + 1}` : `offset ${offset}`;
|
|
516
|
-
return { tokenize, tokenNames, positions, modeNames };
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
idx: 0,
|
|
524
|
-
maxIdx: 0,
|
|
525
|
-
expected: [],
|
|
526
|
-
grammar,
|
|
527
|
-
memo: options.memo ? /* @__PURE__ */ new Map() : null,
|
|
528
|
-
// Entering a rule is one array store rather than an object allocation, and
|
|
529
|
-
// the stack is snapshotted only on a furthest failure — by the time an error
|
|
530
|
-
// is built it would otherwise have unwound to just the start rule.
|
|
531
|
-
ruleNames: [],
|
|
532
|
-
ruleDepth: 0,
|
|
533
|
-
failureStack: null,
|
|
534
|
-
track: options.track === true,
|
|
535
|
-
steps: 0,
|
|
536
|
-
maxSteps: options.maxSteps || 0
|
|
537
|
-
});
|
|
538
|
-
function expect(state, expectation) {
|
|
539
|
-
if (state.idx > state.maxIdx) {
|
|
540
|
-
state.maxIdx = state.idx;
|
|
541
|
-
if (!state.track) return;
|
|
542
|
-
state.expected = [];
|
|
543
|
-
state.failureStack = state.ruleNames.slice(0, state.ruleDepth);
|
|
948
|
+
if (!options.incremental) return { tokenize, tokenNames, positions, modeNames };
|
|
949
|
+
const recover = !!options.recover;
|
|
950
|
+
const errorTokenName = options.errorToken || "Error";
|
|
951
|
+
function snapshotStack(stack, depth) {
|
|
952
|
+
const ids = new Array(depth);
|
|
953
|
+
for (let i = 0; i < depth; i++) ids[i] = stack[i].id;
|
|
954
|
+
return ids;
|
|
544
955
|
}
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
/**
|
|
550
|
-
* @param {string} message
|
|
551
|
-
* @param {{ token: import('./lexer.js').Token | null, expected: string[], ruleStack: string[] }} details
|
|
552
|
-
*/
|
|
553
|
-
constructor(message, details) {
|
|
554
|
-
super(message);
|
|
555
|
-
this.name = "ParseError";
|
|
556
|
-
this.token = details.token;
|
|
557
|
-
this.expected = details.expected;
|
|
558
|
-
this.ruleStack = details.ruleStack;
|
|
956
|
+
function sameStack(a, b) {
|
|
957
|
+
if (a.length !== b.length) return false;
|
|
958
|
+
for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
|
|
959
|
+
return true;
|
|
559
960
|
}
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
961
|
+
function scan(text, offset, mode, stack, depth, line, lineStart) {
|
|
962
|
+
const tokens = [];
|
|
963
|
+
const checkpoints = [];
|
|
964
|
+
const length = text.length;
|
|
965
|
+
outer: while (offset < length) {
|
|
966
|
+
checkpoints.push({ modeId: mode.id, stack: snapshotStack(stack, depth), line, lineStart, offset });
|
|
967
|
+
const applicable = mode.dispatch ? candidates(mode, text.charCodeAt(offset)) : mode.members;
|
|
968
|
+
for (let i = 0; i < applicable.length; i++) {
|
|
969
|
+
let def = applicable[i];
|
|
970
|
+
let end = -1;
|
|
971
|
+
let matched = null;
|
|
972
|
+
if (def.members !== null) {
|
|
973
|
+
end = fusedEnd(def, text, offset);
|
|
974
|
+
if (end === -1) continue;
|
|
975
|
+
def = fusedMember;
|
|
976
|
+
matched = fusedText;
|
|
977
|
+
} else if (def.literal !== void 0) {
|
|
978
|
+
if (text.startsWith(def.literal, offset)) end = offset + def.literal.length;
|
|
979
|
+
} else {
|
|
980
|
+
end = def.scanner === null ? DEFER : scanEnd(def.scanner, text, offset, length);
|
|
981
|
+
if (end === DEFER) {
|
|
982
|
+
end = -1;
|
|
983
|
+
if (def.skip) {
|
|
984
|
+
def.regex.lastIndex = offset;
|
|
985
|
+
if (def.regex.test(text) && def.regex.lastIndex > offset) end = def.regex.lastIndex;
|
|
986
|
+
} else {
|
|
987
|
+
def.regex.lastIndex = offset;
|
|
988
|
+
const match = def.regex.exec(text);
|
|
989
|
+
if (match !== null && match[0].length > 0) matched = match[0];
|
|
990
|
+
if (matched !== null) end = offset + matched.length;
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
if (end === -1) continue;
|
|
995
|
+
let winner = def;
|
|
996
|
+
if (def.longerAltDef) {
|
|
997
|
+
const altEnd = longerAltEnd(def, text, offset);
|
|
998
|
+
if (altEnd > end) {
|
|
999
|
+
end = altEnd;
|
|
1000
|
+
winner = def.longerAltDef;
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
if (!winner.skip) {
|
|
1004
|
+
const image = winner.literal !== void 0 ? winner.literal : winner === def && matched !== null ? matched : text.slice(offset, end);
|
|
1005
|
+
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 });
|
|
1006
|
+
}
|
|
1007
|
+
if (trackLines) {
|
|
1008
|
+
for (let j = offset; j < end; j++) {
|
|
1009
|
+
if (text.charCodeAt(j) !== 10) continue;
|
|
1010
|
+
line++;
|
|
1011
|
+
lineStart = j + 1;
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
if (multiMode) {
|
|
1015
|
+
if (winner.popMode) {
|
|
1016
|
+
if (depth === 0) {
|
|
1017
|
+
if (!recover) {
|
|
1018
|
+
throw new LexError(
|
|
1019
|
+
`Token '${winner.name}' popped the initial lexer mode at ${describe(offset, line, lineStart)}`,
|
|
1020
|
+
{ offset, line: trackLines ? line : 0, col: trackLines ? offset - lineStart + 1 : 0 }
|
|
1021
|
+
);
|
|
1022
|
+
}
|
|
1023
|
+
} else {
|
|
1024
|
+
mode = stack[--depth];
|
|
1025
|
+
}
|
|
1026
|
+
} else if (winner.pushMode !== -1) {
|
|
1027
|
+
stack[depth++] = mode;
|
|
1028
|
+
mode = modes[winner.pushMode];
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
offset = end;
|
|
1032
|
+
continue outer;
|
|
1033
|
+
}
|
|
1034
|
+
if (recover) {
|
|
1035
|
+
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 });
|
|
1036
|
+
if (trackLines && text.charCodeAt(offset) === 10) {
|
|
1037
|
+
line++;
|
|
1038
|
+
lineStart = offset + 1;
|
|
1039
|
+
}
|
|
1040
|
+
offset++;
|
|
1041
|
+
continue outer;
|
|
1042
|
+
}
|
|
1043
|
+
const column = offset - lineStart + 1;
|
|
1044
|
+
throw new LexError(
|
|
1045
|
+
trackLines ? `Unexpected character '${text[offset]}' at line ${line} column ${column}${inMode(mode)}` : `Unexpected character '${text[offset]}' at offset ${offset}${inMode(mode)}`,
|
|
1046
|
+
{ offset, line: trackLines ? line : 0, col: trackLines ? column : 0 }
|
|
1047
|
+
);
|
|
1048
|
+
}
|
|
1049
|
+
return { tokens, checkpoints, mode, stack, depth, line, lineStart };
|
|
1050
|
+
}
|
|
1051
|
+
function tokenizeIncremental(text) {
|
|
1052
|
+
if (typeof text !== "string") throw new TypeError("tokenizeIncremental: expected a string");
|
|
1053
|
+
const result = scan(text, 0, initial, multiMode ? [] : [], 0, 1, 0);
|
|
1054
|
+
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 } };
|
|
1055
|
+
}
|
|
1056
|
+
function tokenFloor(tokens, offset) {
|
|
1057
|
+
let lo = 0;
|
|
1058
|
+
let hi = tokens.length;
|
|
1059
|
+
while (lo < hi) {
|
|
1060
|
+
const mid = lo + hi >>> 1;
|
|
1061
|
+
if (tokens[mid].start < offset) lo = mid + 1;
|
|
1062
|
+
else hi = mid;
|
|
1063
|
+
}
|
|
1064
|
+
return lo;
|
|
1065
|
+
}
|
|
1066
|
+
function checkpointFloor(checkpoints, eof, offset) {
|
|
1067
|
+
let lo = 0;
|
|
1068
|
+
let hi = checkpoints.length + 1;
|
|
1069
|
+
while (lo < hi) {
|
|
1070
|
+
const mid = lo + hi >>> 1;
|
|
1071
|
+
const at = mid < checkpoints.length ? checkpoints[mid].offset : eof.offset;
|
|
1072
|
+
if (at <= offset) lo = mid + 1;
|
|
1073
|
+
else hi = mid;
|
|
1074
|
+
}
|
|
1075
|
+
return lo - 1;
|
|
1076
|
+
}
|
|
1077
|
+
function checkpointAt(checkpoints, eof, offset) {
|
|
1078
|
+
if (eof.offset === offset) return eof;
|
|
1079
|
+
let lo = 0;
|
|
1080
|
+
let hi = checkpoints.length;
|
|
1081
|
+
while (lo < hi) {
|
|
1082
|
+
const mid = lo + hi >>> 1;
|
|
1083
|
+
if (checkpoints[mid].offset < offset) lo = mid + 1;
|
|
1084
|
+
else hi = mid;
|
|
1085
|
+
}
|
|
1086
|
+
return lo < checkpoints.length && checkpoints[lo].offset === offset ? checkpoints[lo] : null;
|
|
1087
|
+
}
|
|
1088
|
+
function relex(newText, prev, edit) {
|
|
1089
|
+
if (typeof newText !== "string") throw new TypeError("relex: expected a string");
|
|
1090
|
+
const tokens = prev.tokens;
|
|
1091
|
+
const checkpoints = prev.checkpoints;
|
|
1092
|
+
const delta = edit.newEnd - edit.oldEnd;
|
|
1093
|
+
let resumeIndex = checkpointFloor(checkpoints, prev.eof, edit.start);
|
|
1094
|
+
if (resumeIndex > 0) resumeIndex--;
|
|
1095
|
+
let boundary = resumeIndex < checkpoints.length ? checkpoints[resumeIndex].offset : prev.eof.offset;
|
|
1096
|
+
let ti = tokenFloor(tokens, boundary);
|
|
1097
|
+
while (ti > 0 && tokens[ti - 1].end === boundary) {
|
|
1098
|
+
ti--;
|
|
1099
|
+
boundary = tokens[ti].start;
|
|
1100
|
+
}
|
|
1101
|
+
if (boundary < (resumeIndex < checkpoints.length ? checkpoints[resumeIndex].offset : prev.eof.offset)) {
|
|
1102
|
+
resumeIndex = checkpointFloor(checkpoints, prev.eof, boundary);
|
|
1103
|
+
}
|
|
1104
|
+
const resume = resumeIndex < checkpoints.length ? checkpoints[resumeIndex] : prev.eof;
|
|
1105
|
+
const resumeMode = modes[resume.modeId];
|
|
1106
|
+
const resumeStack = resume.stack.map((id) => modes[id]);
|
|
1107
|
+
const scanned = scan(newText, resume.offset, resumeMode, resumeStack, resumeStack.length, resume.line, resume.lineStart);
|
|
1108
|
+
const scannedEof = { modeId: scanned.mode.id, stack: snapshotStack(scanned.stack, scanned.depth), line: scanned.line, lineStart: scanned.lineStart, offset: newText.length };
|
|
1109
|
+
const VERIFY_WINDOW = 4;
|
|
1110
|
+
function convergenceVerified(oldOffset, newOffset) {
|
|
1111
|
+
const oldStart = tokenFloor(tokens, oldOffset);
|
|
1112
|
+
const freshStart = tokenFloor(scanned.tokens, newOffset);
|
|
1113
|
+
for (let k = 0; k < VERIFY_WINDOW; k++) {
|
|
1114
|
+
const oldTok = tokens[oldStart + k];
|
|
1115
|
+
const freshTok = scanned.tokens[freshStart + k];
|
|
1116
|
+
if (oldTok === void 0 && freshTok === void 0) return true;
|
|
1117
|
+
if (oldTok === void 0 || freshTok === void 0) return false;
|
|
1118
|
+
if (oldTok.type !== freshTok.type || oldTok.image !== freshTok.image) return false;
|
|
1119
|
+
}
|
|
1120
|
+
return true;
|
|
1121
|
+
}
|
|
1122
|
+
let convergeNewOffset = -1;
|
|
1123
|
+
let convergeOldOffset = -1;
|
|
1124
|
+
let convergeOldCheckpoint = null;
|
|
1125
|
+
let freshCheckpointCount = 0;
|
|
1126
|
+
for (let s = 0; s <= scanned.checkpoints.length; s++) {
|
|
1127
|
+
const cp = s < scanned.checkpoints.length ? scanned.checkpoints[s] : scannedEof;
|
|
1128
|
+
if (cp.offset < edit.newEnd) continue;
|
|
1129
|
+
const oldOffset = cp.offset - delta;
|
|
1130
|
+
const oldCp = checkpointAt(checkpoints, prev.eof, oldOffset);
|
|
1131
|
+
if (oldCp && oldCp.modeId === cp.modeId && sameStack(oldCp.stack, cp.stack) && convergenceVerified(oldOffset, cp.offset)) {
|
|
1132
|
+
convergeNewOffset = cp.offset;
|
|
1133
|
+
convergeOldOffset = oldOffset;
|
|
1134
|
+
convergeOldCheckpoint = oldCp;
|
|
1135
|
+
freshCheckpointCount = s;
|
|
1136
|
+
break;
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
const tokenFrom = tokenFloor(tokens, resume.offset);
|
|
1140
|
+
const checkpointFrom = resumeIndex;
|
|
1141
|
+
if (convergeNewOffset === -1) {
|
|
1142
|
+
tokens.splice(tokenFrom, tokens.length - tokenFrom, ...scanned.tokens);
|
|
1143
|
+
checkpoints.splice(checkpointFrom, checkpoints.length - checkpointFrom, ...scanned.checkpoints);
|
|
1144
|
+
prev.text = newText;
|
|
1145
|
+
prev.eof = scannedEof;
|
|
1146
|
+
return prev;
|
|
1147
|
+
}
|
|
1148
|
+
const freshTokenCount = tokenFloor(scanned.tokens, convergeNewOffset);
|
|
1149
|
+
const lineDelta = trackLines ? scanned.checkpoints.length > freshCheckpointCount ? scanned.checkpoints[freshCheckpointCount].line - convergeOldCheckpoint.line : scannedEof.line - convergeOldCheckpoint.line : 0;
|
|
1150
|
+
const boundaryOldLineStart = convergeOldCheckpoint.lineStart;
|
|
1151
|
+
const boundaryNewLineStart = (freshCheckpointCount < scanned.checkpoints.length ? scanned.checkpoints[freshCheckpointCount] : scannedEof).lineStart;
|
|
1152
|
+
const shiftedLineStart = (oldLineStart) => oldLineStart === boundaryOldLineStart ? boundaryNewLineStart : oldLineStart + delta;
|
|
1153
|
+
const tailTokenFrom = tokenFloor(tokens, convergeOldOffset);
|
|
1154
|
+
for (let i = tailTokenFrom; i < tokens.length; i++) {
|
|
1155
|
+
const t = tokens[i];
|
|
1156
|
+
if (trackLines) {
|
|
1157
|
+
const oldLineStart = t.start - (t.col - 1);
|
|
1158
|
+
t.col = t.start + delta - shiftedLineStart(oldLineStart) + 1;
|
|
1159
|
+
t.line += lineDelta;
|
|
1160
|
+
}
|
|
1161
|
+
t.start += delta;
|
|
1162
|
+
t.end += delta;
|
|
1163
|
+
}
|
|
1164
|
+
tokens.splice(tokenFrom, tailTokenFrom - tokenFrom, ...scanned.tokens.slice(0, freshTokenCount));
|
|
1165
|
+
const tailCheckpointFrom = checkpointFloor(checkpoints, prev.eof, convergeOldOffset - 1) + 1;
|
|
1166
|
+
for (let i = tailCheckpointFrom; i < checkpoints.length; i++) {
|
|
1167
|
+
const cp = checkpoints[i];
|
|
1168
|
+
cp.lineStart = shiftedLineStart(cp.lineStart);
|
|
1169
|
+
cp.line += lineDelta;
|
|
1170
|
+
cp.offset += delta;
|
|
1171
|
+
}
|
|
1172
|
+
checkpoints.splice(checkpointFrom, tailCheckpointFrom - checkpointFrom, ...scanned.checkpoints.slice(0, freshCheckpointCount));
|
|
1173
|
+
prev.text = newText;
|
|
1174
|
+
prev.eof.lineStart = shiftedLineStart(prev.eof.lineStart);
|
|
1175
|
+
prev.eof.line += lineDelta;
|
|
1176
|
+
prev.eof.offset += delta;
|
|
1177
|
+
return prev;
|
|
1178
|
+
}
|
|
1179
|
+
return { tokenize, tokenizeIncremental, relex, tokenNames, positions, modeNames };
|
|
572
1180
|
}
|
|
573
1181
|
|
|
574
1182
|
// src/analyze.js
|
|
@@ -601,6 +1209,7 @@ var PARSER_OPS = /* @__PURE__ */ new Set([
|
|
|
601
1209
|
"many",
|
|
602
1210
|
"many1",
|
|
603
1211
|
"option",
|
|
1212
|
+
"flag",
|
|
604
1213
|
"manySep",
|
|
605
1214
|
"many1Sep",
|
|
606
1215
|
"infixLeft",
|
|
@@ -608,6 +1217,8 @@ var PARSER_OPS = /* @__PURE__ */ new Set([
|
|
|
608
1217
|
"postfix",
|
|
609
1218
|
"prefix",
|
|
610
1219
|
"between",
|
|
1220
|
+
"pick",
|
|
1221
|
+
"yields",
|
|
611
1222
|
"as",
|
|
612
1223
|
"oneOf",
|
|
613
1224
|
"eof",
|
|
@@ -661,7 +1272,8 @@ function firstOf(node, ruleFirsts) {
|
|
|
661
1272
|
return result;
|
|
662
1273
|
}
|
|
663
1274
|
case "many":
|
|
664
|
-
case "option":
|
|
1275
|
+
case "option":
|
|
1276
|
+
case "flag": {
|
|
665
1277
|
const inner = firstOf(arg, ruleFirsts);
|
|
666
1278
|
return { tokens: inner.tokens, nullable: true, unknown: inner.unknown };
|
|
667
1279
|
}
|
|
@@ -684,6 +1296,9 @@ function firstOf(node, ruleFirsts) {
|
|
|
684
1296
|
return firstOf(Array.isArray(arg) ? arg[0] : arg && arg.parser, ruleFirsts);
|
|
685
1297
|
case "between":
|
|
686
1298
|
return firstOf({ seq: [arg && arg.open, arg && arg.body, arg && arg.close] }, ruleFirsts);
|
|
1299
|
+
case "pick":
|
|
1300
|
+
case "yields":
|
|
1301
|
+
return firstOf({ seq: arg && Array.isArray(arg.parsers) ? arg.parsers : [] }, ruleFirsts);
|
|
687
1302
|
case "prefix": {
|
|
688
1303
|
const ops = firstOf(arg && arg.operator, ruleFirsts);
|
|
689
1304
|
const inner = firstOf(arg && arg.operand, ruleFirsts);
|
|
@@ -747,12 +1362,94 @@ function recognizeSeqIdiom(children) {
|
|
|
747
1362
|
if (rewritten !== BAIL) return { as: [parser, rewritten] };
|
|
748
1363
|
}
|
|
749
1364
|
}
|
|
1365
|
+
if (children.length >= 2) {
|
|
1366
|
+
const last = children[children.length - 1];
|
|
1367
|
+
const head = children.slice(0, -1);
|
|
1368
|
+
if (opOf(last) === "action" && isConstant(last.action) && head.every(isPlainParser) && !head.some(usesOuterScope)) {
|
|
1369
|
+
return { yields: { parsers: head, value: last.action } };
|
|
1370
|
+
}
|
|
1371
|
+
if (opOf(last) === "action" && opOf(last.action) === "val" && typeof last.action.val === "string") {
|
|
1372
|
+
const name = last.action.val;
|
|
1373
|
+
const labelled = head.map((child, i) => opOf(child) === "label" ? i : -1).filter((i) => i !== -1);
|
|
1374
|
+
if (labelled.length === 1 && head[labelled[0]].label[0] === name && head.every((child, i) => i === labelled[0] || isPlainParser(child)) && !head.some(usesOuterScope)) {
|
|
1375
|
+
return { pick: { parsers: head.map((child, i) => i === labelled[0] ? child.label[1] : child), index: labelled[0] } };
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
750
1379
|
return null;
|
|
751
1380
|
}
|
|
1381
|
+
var isConstant = (value) => value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
1382
|
+
function usesOuterScope(logic) {
|
|
1383
|
+
if (Array.isArray(logic)) return logic.some(usesOuterScope);
|
|
1384
|
+
if (!logic || typeof logic !== "object") return false;
|
|
1385
|
+
for (const key of Object.keys(logic)) {
|
|
1386
|
+
const arg = logic[key];
|
|
1387
|
+
if (key === "val" && Array.isArray(arg) && Array.isArray(arg[0])) return true;
|
|
1388
|
+
if (key === "var") {
|
|
1389
|
+
if (typeof arg === "string" && arg.includes("../")) return true;
|
|
1390
|
+
if (Array.isArray(arg) && typeof arg[0] === "string" && arg[0].includes("../")) return true;
|
|
1391
|
+
}
|
|
1392
|
+
if (usesOuterScope(arg)) return true;
|
|
1393
|
+
}
|
|
1394
|
+
return false;
|
|
1395
|
+
}
|
|
752
1396
|
function isPlainParser(node) {
|
|
753
1397
|
const op = opOf(node);
|
|
754
1398
|
return op !== null && op !== "label" && op !== "action";
|
|
755
1399
|
}
|
|
1400
|
+
function deepEqual(a, b) {
|
|
1401
|
+
if (a === b) return true;
|
|
1402
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
1403
|
+
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
|
|
1404
|
+
for (let i = 0; i < a.length; i++) if (!deepEqual(a[i], b[i])) return false;
|
|
1405
|
+
return true;
|
|
1406
|
+
}
|
|
1407
|
+
if (a === null || b === null || typeof a !== "object" || typeof b !== "object") return false;
|
|
1408
|
+
const keysA = Object.keys(a);
|
|
1409
|
+
const keysB = Object.keys(b);
|
|
1410
|
+
if (keysA.length !== keysB.length) return false;
|
|
1411
|
+
for (const key of keysA) {
|
|
1412
|
+
if (!Object.prototype.hasOwnProperty.call(b, key) || !deepEqual(a[key], b[key])) return false;
|
|
1413
|
+
}
|
|
1414
|
+
return true;
|
|
1415
|
+
}
|
|
1416
|
+
function recognizeAltPrefixIdiom(branches) {
|
|
1417
|
+
if (!Array.isArray(branches) || branches.length < 2) return null;
|
|
1418
|
+
if (!branches.every((b) => opOf(b) === "seq" && Array.isArray(b.seq))) return null;
|
|
1419
|
+
const seqs = branches.map((b) => b.seq);
|
|
1420
|
+
const minLen = Math.min(...seqs.map((s) => s.length));
|
|
1421
|
+
let n = 0;
|
|
1422
|
+
while (n < minLen) {
|
|
1423
|
+
const candidate = seqs[0][n];
|
|
1424
|
+
const op = opOf(candidate);
|
|
1425
|
+
if (op === "label" || op === "action") break;
|
|
1426
|
+
if (!seqs.every((s) => deepEqual(s[n], candidate))) break;
|
|
1427
|
+
n++;
|
|
1428
|
+
}
|
|
1429
|
+
if (n === 0) return null;
|
|
1430
|
+
if (!seqs.every((s) => s.slice(n).some((child) => opOf(child) === "action"))) return null;
|
|
1431
|
+
const prefix = seqs[0].slice(0, n);
|
|
1432
|
+
const remainders = seqs.map((s) => ({ seq: s.slice(n) }));
|
|
1433
|
+
return {
|
|
1434
|
+
seq: [
|
|
1435
|
+
...prefix,
|
|
1436
|
+
{ label: ["__grammatik_rest", { alt: remainders }] },
|
|
1437
|
+
{ action: { val: "__grammatik_rest" } }
|
|
1438
|
+
]
|
|
1439
|
+
};
|
|
1440
|
+
}
|
|
1441
|
+
function recognizeAltConsumeIdiom(branches) {
|
|
1442
|
+
if (!Array.isArray(branches) || branches.length < 2) return null;
|
|
1443
|
+
const names = [];
|
|
1444
|
+
for (const branch of branches) {
|
|
1445
|
+
if (opOf(branch) !== "consume") return null;
|
|
1446
|
+
const arg = branch.consume;
|
|
1447
|
+
const name = typeof arg === "string" ? arg : Array.isArray(arg) && arg.length === 1 ? arg[0] : null;
|
|
1448
|
+
if (typeof name !== "string") return null;
|
|
1449
|
+
names.push(name);
|
|
1450
|
+
}
|
|
1451
|
+
return { oneOf: names };
|
|
1452
|
+
}
|
|
756
1453
|
var BAIL = /* @__PURE__ */ Symbol("grammatik.bail");
|
|
757
1454
|
function rebaseOnLabel(logic, name) {
|
|
758
1455
|
if (Array.isArray(logic)) {
|
|
@@ -819,6 +1516,7 @@ function leftmostCalls(node, ruleFirsts, out) {
|
|
|
819
1516
|
case "many":
|
|
820
1517
|
case "many1":
|
|
821
1518
|
case "option":
|
|
1519
|
+
case "flag":
|
|
822
1520
|
case "lookahead":
|
|
823
1521
|
case "negLookahead":
|
|
824
1522
|
leftmostCalls(arg, ruleFirsts, out);
|
|
@@ -829,6 +1527,10 @@ function leftmostCalls(node, ruleFirsts, out) {
|
|
|
829
1527
|
case "between":
|
|
830
1528
|
if (arg) leftmostCalls({ seq: [arg.open, arg.body, arg.close] }, ruleFirsts, out);
|
|
831
1529
|
break;
|
|
1530
|
+
case "pick":
|
|
1531
|
+
case "yields":
|
|
1532
|
+
if (arg && Array.isArray(arg.parsers)) leftmostCalls({ seq: arg.parsers }, ruleFirsts, out);
|
|
1533
|
+
break;
|
|
832
1534
|
case "prefix":
|
|
833
1535
|
if (arg) {
|
|
834
1536
|
leftmostCalls(arg.operator, ruleFirsts, out);
|
|
@@ -895,6 +1597,7 @@ function validateNode(node, grammar, tokenNames, errors, path) {
|
|
|
895
1597
|
case "many":
|
|
896
1598
|
case "many1":
|
|
897
1599
|
case "option":
|
|
1600
|
+
case "flag":
|
|
898
1601
|
case "text":
|
|
899
1602
|
case "lookahead":
|
|
900
1603
|
case "negLookahead":
|
|
@@ -916,6 +1619,26 @@ function validateNode(node, grammar, tokenNames, errors, path) {
|
|
|
916
1619
|
}
|
|
917
1620
|
break;
|
|
918
1621
|
}
|
|
1622
|
+
case "pick": {
|
|
1623
|
+
if (!arg || !Array.isArray(arg.parsers) || arg.parsers.length === 0) {
|
|
1624
|
+
errors.push(`${path}: 'pick' expects { parsers: [parser, ...], index }`);
|
|
1625
|
+
} else if (!Number.isInteger(arg.index) || arg.index < 0 || arg.index >= arg.parsers.length) {
|
|
1626
|
+
errors.push(`${path}: 'pick' index ${JSON.stringify(arg.index)} is out of range for ${arg.parsers.length} parser(s)`);
|
|
1627
|
+
} else {
|
|
1628
|
+
arg.parsers.forEach((c, i) => validateNode(c, grammar, tokenNames, errors, `${path}.pick[${i}]`));
|
|
1629
|
+
}
|
|
1630
|
+
break;
|
|
1631
|
+
}
|
|
1632
|
+
case "yields": {
|
|
1633
|
+
if (!arg || !Array.isArray(arg.parsers) || arg.parsers.length === 0 || !("value" in arg)) {
|
|
1634
|
+
errors.push(`${path}: 'yields' expects { parsers: [parser, ...], value }`);
|
|
1635
|
+
} else if (!isConstant(arg.value)) {
|
|
1636
|
+
errors.push(`${path}: 'yields' value must be a string, number, boolean or null, got ${JSON.stringify(arg.value)}`);
|
|
1637
|
+
} else {
|
|
1638
|
+
arg.parsers.forEach((c, i) => validateNode(c, grammar, tokenNames, errors, `${path}.yields[${i}]`));
|
|
1639
|
+
}
|
|
1640
|
+
break;
|
|
1641
|
+
}
|
|
919
1642
|
case "prefix": {
|
|
920
1643
|
if (!arg || typeof arg !== "object" || !("operator" in arg) || !("operand" in arg) || !("combine" in arg)) {
|
|
921
1644
|
errors.push(`${path}: 'prefix' expects { operator, operand, combine }`);
|
|
@@ -1002,16 +1725,38 @@ function analyze(grammar, options = {}) {
|
|
|
1002
1725
|
return { firsts, errors, leftRecursive };
|
|
1003
1726
|
}
|
|
1004
1727
|
function buildDispatch(branches, firsts) {
|
|
1005
|
-
const
|
|
1006
|
-
for (const
|
|
1007
|
-
const first = firstOf(branch, firsts);
|
|
1728
|
+
const perBranch = branches.map((branch) => firstOf(branch, firsts));
|
|
1729
|
+
for (const first of perBranch) {
|
|
1008
1730
|
if (first.nullable || first.unknown || first.tokens.size === 0) return null;
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1731
|
+
}
|
|
1732
|
+
const byToken = /* @__PURE__ */ new Map();
|
|
1733
|
+
for (let i = 0; i < branches.length; i++) {
|
|
1734
|
+
for (const token of perBranch[i].tokens) {
|
|
1735
|
+
let list = byToken.get(token);
|
|
1736
|
+
if (list === void 0) {
|
|
1737
|
+
list = [];
|
|
1738
|
+
byToken.set(token, list);
|
|
1739
|
+
}
|
|
1740
|
+
list.push(branches[i]);
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
if (byToken.size === 0) return null;
|
|
1744
|
+
const canonical = /* @__PURE__ */ new Map();
|
|
1745
|
+
const table = /* @__PURE__ */ new Map();
|
|
1746
|
+
for (const [token, list] of byToken) {
|
|
1747
|
+
if (list.length === 1) {
|
|
1748
|
+
table.set(token, list[0]);
|
|
1749
|
+
continue;
|
|
1750
|
+
}
|
|
1751
|
+
const key = list.map((branch) => branches.indexOf(branch)).join(",");
|
|
1752
|
+
let group = canonical.get(key);
|
|
1753
|
+
if (group === void 0) {
|
|
1754
|
+
group = list;
|
|
1755
|
+
canonical.set(key, group);
|
|
1012
1756
|
}
|
|
1757
|
+
table.set(token, group);
|
|
1013
1758
|
}
|
|
1014
|
-
return table
|
|
1759
|
+
return table;
|
|
1015
1760
|
}
|
|
1016
1761
|
|
|
1017
1762
|
// src/plan.js
|
|
@@ -1020,20 +1765,6 @@ function declareSync(fn) {
|
|
|
1020
1765
|
fn[SYNC] = true;
|
|
1021
1766
|
return fn;
|
|
1022
1767
|
}
|
|
1023
|
-
function usesOuterScope(logic) {
|
|
1024
|
-
if (Array.isArray(logic)) return logic.some(usesOuterScope);
|
|
1025
|
-
if (!logic || typeof logic !== "object") return false;
|
|
1026
|
-
for (const key of Object.keys(logic)) {
|
|
1027
|
-
const arg = logic[key];
|
|
1028
|
-
if (key === "val" && Array.isArray(arg) && Array.isArray(arg[0])) return true;
|
|
1029
|
-
if (key === "var") {
|
|
1030
|
-
if (typeof arg === "string" && arg.includes("../")) return true;
|
|
1031
|
-
if (Array.isArray(arg) && typeof arg[0] === "string" && arg[0].includes("../")) return true;
|
|
1032
|
-
}
|
|
1033
|
-
if (usesOuterScope(arg)) return true;
|
|
1034
|
-
}
|
|
1035
|
-
return false;
|
|
1036
|
-
}
|
|
1037
1768
|
function mayReturnContext(logic) {
|
|
1038
1769
|
if (Array.isArray(logic)) return logic.some(mayReturnContext);
|
|
1039
1770
|
if (!logic || typeof logic !== "object") return false;
|
|
@@ -1046,7 +1777,7 @@ function mayReturnContext(logic) {
|
|
|
1046
1777
|
return false;
|
|
1047
1778
|
}
|
|
1048
1779
|
function createPlanner(config) {
|
|
1049
|
-
const { engine, rules, firsts, memo, maxSteps, unsafeEval = true } = config;
|
|
1780
|
+
const { engine, rules, firsts, memo, maxSteps, unsafeEval = true, tokenIds = null, memoSlots = /* @__PURE__ */ new Map(), idioms = true } = config;
|
|
1050
1781
|
const slots = /* @__PURE__ */ new Map();
|
|
1051
1782
|
const planCache = /* @__PURE__ */ new WeakMap();
|
|
1052
1783
|
const opCaches = /* @__PURE__ */ new Map();
|
|
@@ -1129,9 +1860,10 @@ function createPlanner(config) {
|
|
|
1129
1860
|
}
|
|
1130
1861
|
const buildConsume = (arg) => {
|
|
1131
1862
|
const name = typeof arg === "string" ? arg : arg[0];
|
|
1863
|
+
const id = tokenIds ? tokenIds.get(name) : void 0;
|
|
1132
1864
|
return (state) => {
|
|
1133
1865
|
const token = state.tokens[state.idx];
|
|
1134
|
-
if (token !== void 0 && token.type === name) {
|
|
1866
|
+
if (token !== void 0 && (token.id === id || token.id === void 0 && token.type === name)) {
|
|
1135
1867
|
state.idx++;
|
|
1136
1868
|
return token;
|
|
1137
1869
|
}
|
|
@@ -1192,6 +1924,17 @@ function createPlanner(config) {
|
|
|
1192
1924
|
return value;
|
|
1193
1925
|
};
|
|
1194
1926
|
};
|
|
1927
|
+
const buildFlag = (arg) => {
|
|
1928
|
+
const child = plan(arg);
|
|
1929
|
+
return (state) => {
|
|
1930
|
+
const start = state.idx;
|
|
1931
|
+
if (child(state) === FAIL) {
|
|
1932
|
+
state.idx = start;
|
|
1933
|
+
return false;
|
|
1934
|
+
}
|
|
1935
|
+
return true;
|
|
1936
|
+
};
|
|
1937
|
+
};
|
|
1195
1938
|
const buildInfixLeft = (arg) => {
|
|
1196
1939
|
const operand = plan(arg.operand);
|
|
1197
1940
|
const operator = arg.operator === void 0 ? null : plan(arg.operator);
|
|
@@ -1227,10 +1970,11 @@ function createPlanner(config) {
|
|
|
1227
1970
|
const buildOneOf = (arg) => {
|
|
1228
1971
|
const names = Array.isArray(arg) ? arg : [arg];
|
|
1229
1972
|
const set = new Set(names);
|
|
1973
|
+
const idSet = tokenIds ? new Set(names.map((n) => tokenIds.get(n))) : null;
|
|
1230
1974
|
const count = names.length;
|
|
1231
1975
|
return (state) => {
|
|
1232
1976
|
const token = state.tokens[state.idx];
|
|
1233
|
-
if (token !== void 0 && set.has(token.type)) {
|
|
1977
|
+
if (token !== void 0 && (idSet ? token.id !== void 0 ? idSet.has(token.id) : set.has(token.type) : set.has(token.type))) {
|
|
1234
1978
|
state.idx++;
|
|
1235
1979
|
return token;
|
|
1236
1980
|
}
|
|
@@ -1316,6 +2060,26 @@ function createPlanner(config) {
|
|
|
1316
2060
|
return value;
|
|
1317
2061
|
};
|
|
1318
2062
|
};
|
|
2063
|
+
const buildSequence = (arg, pickIndex, constant) => {
|
|
2064
|
+
const fns = arg.parsers.map(plan);
|
|
2065
|
+
const count = fns.length;
|
|
2066
|
+
return (state) => {
|
|
2067
|
+
tick(state);
|
|
2068
|
+
const start = state.idx;
|
|
2069
|
+
let picked;
|
|
2070
|
+
for (let i = 0; i < count; i++) {
|
|
2071
|
+
const value = fns[i](state);
|
|
2072
|
+
if (value === FAIL) {
|
|
2073
|
+
state.idx = start;
|
|
2074
|
+
return FAIL;
|
|
2075
|
+
}
|
|
2076
|
+
if (i === pickIndex) picked = value;
|
|
2077
|
+
}
|
|
2078
|
+
return pickIndex === -1 ? constant : picked;
|
|
2079
|
+
};
|
|
2080
|
+
};
|
|
2081
|
+
const buildPick = (arg) => buildSequence(arg, arg.index, void 0);
|
|
2082
|
+
const buildYields = (arg) => buildSequence(arg, -1, arg.value);
|
|
1319
2083
|
const buildPrefix = (arg) => {
|
|
1320
2084
|
const operator = plan(arg.operator);
|
|
1321
2085
|
const operand = plan(arg.operand);
|
|
@@ -1399,6 +2163,31 @@ function createPlanner(config) {
|
|
|
1399
2163
|
return value;
|
|
1400
2164
|
};
|
|
1401
2165
|
}
|
|
2166
|
+
const memoSlot = memoSlots.get(name);
|
|
2167
|
+
if (memoSlot !== void 0) {
|
|
2168
|
+
return (state) => {
|
|
2169
|
+
tick(state);
|
|
2170
|
+
const table = state.rm[memoSlot];
|
|
2171
|
+
const at = state.idx;
|
|
2172
|
+
const end = table.ends[at];
|
|
2173
|
+
if (end !== 0) {
|
|
2174
|
+
state.idx = end - 1;
|
|
2175
|
+
return table.vals[at];
|
|
2176
|
+
}
|
|
2177
|
+
let depth = 0;
|
|
2178
|
+
if (state.track) {
|
|
2179
|
+
depth = state.ruleDepth;
|
|
2180
|
+
state.ruleNames[depth] = name;
|
|
2181
|
+
state.ruleDepth = depth + 1;
|
|
2182
|
+
}
|
|
2183
|
+
const value = slot.fn(state);
|
|
2184
|
+
if (state.track) state.ruleDepth = depth;
|
|
2185
|
+
if (value === FAIL) state.idx = at;
|
|
2186
|
+
table.ends[at] = state.idx + 1;
|
|
2187
|
+
table.vals[at] = value;
|
|
2188
|
+
return value;
|
|
2189
|
+
};
|
|
2190
|
+
}
|
|
1402
2191
|
return (state) => {
|
|
1403
2192
|
tick(state);
|
|
1404
2193
|
if (!state.track) return slot.fn(state);
|
|
@@ -1477,6 +2266,9 @@ function createPlanner(config) {
|
|
|
1477
2266
|
infixRight: buildInfixRight,
|
|
1478
2267
|
as: buildAs,
|
|
1479
2268
|
between: buildBetween,
|
|
2269
|
+
flag: buildFlag,
|
|
2270
|
+
pick: buildPick,
|
|
2271
|
+
yields: buildYields,
|
|
1480
2272
|
prefix: buildPrefix,
|
|
1481
2273
|
postfix: buildPostfix,
|
|
1482
2274
|
manySep: (arg) => planSeparated(arg, false),
|
|
@@ -1496,7 +2288,7 @@ function createPlanner(config) {
|
|
|
1496
2288
|
}
|
|
1497
2289
|
function planSeq(children) {
|
|
1498
2290
|
if (!Array.isArray(children)) throw new Error("'seq' expects an array of parsers");
|
|
1499
|
-
const idiom = recognizeSeqIdiom(children);
|
|
2291
|
+
const idiom = idioms ? recognizeSeqIdiom(children) : null;
|
|
1500
2292
|
if (idiom !== null) return planUncached(idiom);
|
|
1501
2293
|
const PARSER = 0;
|
|
1502
2294
|
const LABELLED = 1;
|
|
@@ -1506,6 +2298,7 @@ function createPlanner(config) {
|
|
|
1506
2298
|
const kinds = [];
|
|
1507
2299
|
const names = [];
|
|
1508
2300
|
const tokens = [];
|
|
2301
|
+
const tokenIdList = [];
|
|
1509
2302
|
const fns = [];
|
|
1510
2303
|
const actionLogic = [];
|
|
1511
2304
|
let hasAction = false;
|
|
@@ -1526,11 +2319,13 @@ function createPlanner(config) {
|
|
|
1526
2319
|
kinds.push(LABELLED_TERMINAL);
|
|
1527
2320
|
names.push(name);
|
|
1528
2321
|
tokens.push(terminal2);
|
|
2322
|
+
tokenIdList.push(tokenIds ? tokenIds.get(terminal2) : void 0);
|
|
1529
2323
|
fns.push(null);
|
|
1530
2324
|
} else {
|
|
1531
2325
|
kinds.push(LABELLED);
|
|
1532
2326
|
names.push(name);
|
|
1533
2327
|
tokens.push(null);
|
|
2328
|
+
tokenIdList.push(void 0);
|
|
1534
2329
|
fns.push(plan(inner));
|
|
1535
2330
|
}
|
|
1536
2331
|
continue;
|
|
@@ -1539,6 +2334,7 @@ function createPlanner(config) {
|
|
|
1539
2334
|
kinds.push(ACTION);
|
|
1540
2335
|
names.push(null);
|
|
1541
2336
|
tokens.push(null);
|
|
2337
|
+
tokenIdList.push(void 0);
|
|
1542
2338
|
fns.push(planAction(child.action));
|
|
1543
2339
|
actionLogic.push(child.action);
|
|
1544
2340
|
hasAction = true;
|
|
@@ -1549,11 +2345,13 @@ function createPlanner(config) {
|
|
|
1549
2345
|
kinds.push(TERMINAL);
|
|
1550
2346
|
names.push(null);
|
|
1551
2347
|
tokens.push(terminal);
|
|
2348
|
+
tokenIdList.push(tokenIds ? tokenIds.get(terminal) : void 0);
|
|
1552
2349
|
fns.push(null);
|
|
1553
2350
|
} else {
|
|
1554
2351
|
kinds.push(PARSER);
|
|
1555
2352
|
names.push(null);
|
|
1556
2353
|
tokens.push(null);
|
|
2354
|
+
tokenIdList.push(void 0);
|
|
1557
2355
|
fns.push(plan(child));
|
|
1558
2356
|
}
|
|
1559
2357
|
}
|
|
@@ -1574,7 +2372,7 @@ function createPlanner(config) {
|
|
|
1574
2372
|
let value;
|
|
1575
2373
|
if (kind === TERMINAL || kind === LABELLED_TERMINAL) {
|
|
1576
2374
|
const token = state.tokens[state.idx];
|
|
1577
|
-
if (token !== void 0 && token.type === tokens[i]) {
|
|
2375
|
+
if (token !== void 0 && (token.id === tokenIdList[i] || token.id === void 0 && token.type === tokens[i])) {
|
|
1578
2376
|
state.idx++;
|
|
1579
2377
|
value = token;
|
|
1580
2378
|
} else {
|
|
@@ -1604,18 +2402,46 @@ function createPlanner(config) {
|
|
|
1604
2402
|
}
|
|
1605
2403
|
function planAlt(branches) {
|
|
1606
2404
|
if (!Array.isArray(branches)) throw new Error("'alt' expects an array of branches");
|
|
2405
|
+
const oneOf = idioms ? recognizeAltConsumeIdiom(branches) : null;
|
|
2406
|
+
if (oneOf !== null) return planUncached(oneOf);
|
|
2407
|
+
const factored = idioms ? recognizeAltPrefixIdiom(branches) : null;
|
|
2408
|
+
if (factored !== null) return planUncached(factored);
|
|
1607
2409
|
const planned = branches.map(plan);
|
|
1608
2410
|
const table = firsts ? buildDispatch(branches, firsts) : null;
|
|
1609
2411
|
if (table !== null) {
|
|
1610
2412
|
const byToken = /* @__PURE__ */ Object.create(null);
|
|
1611
|
-
|
|
2413
|
+
const byId = tokenIds ? [] : null;
|
|
2414
|
+
const groupClosures = /* @__PURE__ */ new Map();
|
|
2415
|
+
for (const [token, branch] of table) {
|
|
2416
|
+
let closure = groupClosures.get(branch);
|
|
2417
|
+
if (closure === void 0) {
|
|
2418
|
+
closure = Array.isArray(branch) ? (() => {
|
|
2419
|
+
const group = branch.map((b) => planned[branches.indexOf(b)]);
|
|
2420
|
+
return (state) => {
|
|
2421
|
+
const start = state.idx;
|
|
2422
|
+
for (let i = 0; i < group.length; i++) {
|
|
2423
|
+
const value = group[i](state);
|
|
2424
|
+
if (value !== FAIL) return value;
|
|
2425
|
+
state.idx = start;
|
|
2426
|
+
}
|
|
2427
|
+
return FAIL;
|
|
2428
|
+
};
|
|
2429
|
+
})() : planned[branches.indexOf(branch)];
|
|
2430
|
+
groupClosures.set(branch, closure);
|
|
2431
|
+
}
|
|
2432
|
+
byToken[token] = closure;
|
|
2433
|
+
if (byId) {
|
|
2434
|
+
const id = tokenIds.get(token);
|
|
2435
|
+
if (id !== void 0) byId[id] = closure;
|
|
2436
|
+
}
|
|
2437
|
+
}
|
|
1612
2438
|
const expectedTokens = [...table.keys()];
|
|
1613
2439
|
const expectedCount = expectedTokens.length;
|
|
1614
2440
|
return (state) => {
|
|
1615
2441
|
tick(state);
|
|
1616
2442
|
const token = state.tokens[state.idx];
|
|
1617
2443
|
if (token !== void 0) {
|
|
1618
|
-
const branch = byToken[token.type];
|
|
2444
|
+
const branch = byId && token.id !== void 0 ? byId[token.id] : byToken[token.type];
|
|
1619
2445
|
if (branch !== void 0) return branch(state);
|
|
1620
2446
|
}
|
|
1621
2447
|
for (let i = 0; i < expectedCount; i++) expect(state, expectedTokens[i]);
|
|
@@ -1700,6 +2526,9 @@ var OPERATORS = [
|
|
|
1700
2526
|
"prefix",
|
|
1701
2527
|
"postfix",
|
|
1702
2528
|
"between",
|
|
2529
|
+
"pick",
|
|
2530
|
+
"yields",
|
|
2531
|
+
"flag",
|
|
1703
2532
|
"as",
|
|
1704
2533
|
"subrule",
|
|
1705
2534
|
"label",
|
|
@@ -1754,7 +2583,12 @@ var createValueMethods = () => ({
|
|
|
1754
2583
|
|
|
1755
2584
|
// src/codegen.js
|
|
1756
2585
|
function createCodegen(config) {
|
|
1757
|
-
const { planner, rules, firsts, memo, maxSteps } = config;
|
|
2586
|
+
const { planner, rules, firsts, memo, maxSteps, tokenIds = null, memoSlots = /* @__PURE__ */ new Map(), inlineRules = true, idioms = true } = config;
|
|
2587
|
+
const typeCheck = (tokenVar, name) => {
|
|
2588
|
+
const id = tokenIds ? tokenIds.get(name) : void 0;
|
|
2589
|
+
if (id === void 0) return `${tokenVar}.type === ${JSON.stringify(name)}`;
|
|
2590
|
+
return `(${tokenVar}.id === ${id} || (${tokenVar}.id === undefined && ${tokenVar}.type === ${JSON.stringify(name)}))`;
|
|
2591
|
+
};
|
|
1758
2592
|
const HELPERS = [];
|
|
1759
2593
|
const ORIGINS = [];
|
|
1760
2594
|
const RULES = [];
|
|
@@ -1770,6 +2604,33 @@ function createCodegen(config) {
|
|
|
1770
2604
|
}
|
|
1771
2605
|
const actionHelper = (logic) => helper(planner.planAction(logic), { kind: "action", logic });
|
|
1772
2606
|
const constantHelper = (node) => helper(planner.plan(node), { kind: "constant", node });
|
|
2607
|
+
const MAX_INLINE_DEPTH = 6;
|
|
2608
|
+
const ONE_OF_SWITCH_AT = 12;
|
|
2609
|
+
const INLINE_OPS = /* @__PURE__ */ new Set(["infixLeft", "infixRight", "prefix", "postfix"]);
|
|
2610
|
+
const callSites = /* @__PURE__ */ new Map();
|
|
2611
|
+
(function count(node) {
|
|
2612
|
+
if (Array.isArray(node)) {
|
|
2613
|
+
for (const child of node) count(child);
|
|
2614
|
+
return;
|
|
2615
|
+
}
|
|
2616
|
+
if (!node || typeof node !== "object") return;
|
|
2617
|
+
if ("subrule" in node) {
|
|
2618
|
+
const target = Array.isArray(node.subrule) ? node.subrule[0] : node.subrule;
|
|
2619
|
+
if (typeof target === "string") callSites.set(target, (callSites.get(target) || 0) + 1);
|
|
2620
|
+
}
|
|
2621
|
+
for (const key of Object.keys(node)) count(node[key]);
|
|
2622
|
+
})(rules);
|
|
2623
|
+
const inlining = /* @__PURE__ */ new Set();
|
|
2624
|
+
let coldDepth = 0;
|
|
2625
|
+
const cold = (write) => {
|
|
2626
|
+
coldDepth++;
|
|
2627
|
+
try {
|
|
2628
|
+
return write();
|
|
2629
|
+
} finally {
|
|
2630
|
+
coldDepth--;
|
|
2631
|
+
}
|
|
2632
|
+
};
|
|
2633
|
+
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]));
|
|
1773
2634
|
function indexOfRule(name) {
|
|
1774
2635
|
let index = ruleIndex.get(name);
|
|
1775
2636
|
if (index !== void 0) return index;
|
|
@@ -1796,7 +2657,7 @@ function createCodegen(config) {
|
|
|
1796
2657
|
out.push(
|
|
1797
2658
|
`let ${result};`,
|
|
1798
2659
|
`const ${token} = c.tokens[c.idx];`,
|
|
1799
|
-
`if (${token} !== undefined && ${token
|
|
2660
|
+
`if (${token} !== undefined && ${typeCheck(token, name)}) { c.idx++; ${result} = ${token}; }`,
|
|
1800
2661
|
`else { E(c, ${JSON.stringify(name)}); ${result} = F; }`
|
|
1801
2662
|
);
|
|
1802
2663
|
return result;
|
|
@@ -1830,6 +2691,16 @@ function createCodegen(config) {
|
|
|
1830
2691
|
);
|
|
1831
2692
|
return result;
|
|
1832
2693
|
}
|
|
2694
|
+
case "flag": {
|
|
2695
|
+
const start = nextId();
|
|
2696
|
+
out.push(`let ${result};`, `const ${start} = c.idx;`, "{");
|
|
2697
|
+
const value = gen(arg, out, bindingsVar);
|
|
2698
|
+
out.push(
|
|
2699
|
+
`if (${value} === F) { c.idx = ${start}; ${result} = false; } else ${result} = true;`,
|
|
2700
|
+
"}"
|
|
2701
|
+
);
|
|
2702
|
+
return result;
|
|
2703
|
+
}
|
|
1833
2704
|
case "many":
|
|
1834
2705
|
case "many1": {
|
|
1835
2706
|
const atLeastOne = op === "many1";
|
|
@@ -1847,7 +2718,7 @@ function createCodegen(config) {
|
|
|
1847
2718
|
);
|
|
1848
2719
|
}
|
|
1849
2720
|
out.push("for (;;) {", `const ${before} = c.idx;`);
|
|
1850
|
-
const value = gen(arg, out, bindingsVar);
|
|
2721
|
+
const value = atLeastOne ? cold(() => gen(arg, out, bindingsVar)) : gen(arg, out, bindingsVar);
|
|
1851
2722
|
out.push(
|
|
1852
2723
|
`if (${value} === F) { c.idx = ${before}; break; }`,
|
|
1853
2724
|
`if (c.idx === ${before}) P(${JSON.stringify(op)}, ${before});`,
|
|
@@ -1877,7 +2748,7 @@ function createCodegen(config) {
|
|
|
1877
2748
|
operatorVar = gen(arg.operator, out, bindingsVar);
|
|
1878
2749
|
out.push(`if (${operatorVar} === F) { c.idx = ${mark}; break; }`);
|
|
1879
2750
|
}
|
|
1880
|
-
const right = gen(arg.operand, out, bindingsVar);
|
|
2751
|
+
const right = cold(() => gen(arg.operand, out, bindingsVar));
|
|
1881
2752
|
out.push(
|
|
1882
2753
|
`if (${right} === F) { c.idx = ${mark}; break; }`,
|
|
1883
2754
|
`if (${bindings} === null) ${bindings} = { left: null, op: null, right: null };`,
|
|
@@ -1892,12 +2763,27 @@ function createCodegen(config) {
|
|
|
1892
2763
|
case "oneOf": {
|
|
1893
2764
|
const names = Array.isArray(arg) ? arg : [arg];
|
|
1894
2765
|
const token = nextId();
|
|
1895
|
-
const
|
|
2766
|
+
const ids = tokenIds ? names.map((n) => tokenIds.get(n)) : [];
|
|
2767
|
+
const failed = `else { ${names.map((n) => `E(c, ${JSON.stringify(n)});`).join(" ")} ${result} = F; }`;
|
|
2768
|
+
if (names.length > ONE_OF_SWITCH_AT && ids.every((id) => id !== void 0)) {
|
|
2769
|
+
const hit = nextId();
|
|
2770
|
+
const cases = [...new Set(ids)].map((id) => `case ${id}:`).join(" ");
|
|
2771
|
+
out.push(
|
|
2772
|
+
`let ${result};`,
|
|
2773
|
+
`const ${token} = c.tokens[c.idx];`,
|
|
2774
|
+
`let ${hit} = false;`,
|
|
2775
|
+
`if (${token} !== undefined) { switch (${token}.id !== undefined ? ${token}.id : I[${token}.type]) { ${cases} ${hit} = true; } }`,
|
|
2776
|
+
`if (${hit}) { c.idx++; ${result} = ${token}; }`,
|
|
2777
|
+
failed
|
|
2778
|
+
);
|
|
2779
|
+
return result;
|
|
2780
|
+
}
|
|
2781
|
+
const test = names.map((n) => typeCheck(token, n)).join(" || ");
|
|
1896
2782
|
out.push(
|
|
1897
2783
|
`let ${result};`,
|
|
1898
2784
|
`const ${token} = c.tokens[c.idx];`,
|
|
1899
2785
|
`if (${token} !== undefined && (${test})) { c.idx++; ${result} = ${token}; }`,
|
|
1900
|
-
|
|
2786
|
+
failed
|
|
1901
2787
|
);
|
|
1902
2788
|
return result;
|
|
1903
2789
|
}
|
|
@@ -1930,7 +2816,7 @@ function createCodegen(config) {
|
|
|
1930
2816
|
operatorVar = gen(arg.operator, out, bindingsVar);
|
|
1931
2817
|
out.push(`if (${operatorVar} === F) { c.idx = ${mark}; break; }`);
|
|
1932
2818
|
}
|
|
1933
|
-
const right = gen(arg.operand, out, bindingsVar);
|
|
2819
|
+
const right = cold(() => gen(arg.operand, out, bindingsVar));
|
|
1934
2820
|
out.push(
|
|
1935
2821
|
`if (${right} === F) { c.idx = ${mark}; break; }`,
|
|
1936
2822
|
`if (${items} === null) ${items} = [];`,
|
|
@@ -2005,6 +2891,19 @@ function createCodegen(config) {
|
|
|
2005
2891
|
);
|
|
2006
2892
|
return result;
|
|
2007
2893
|
}
|
|
2894
|
+
case "pick":
|
|
2895
|
+
case "yields": {
|
|
2896
|
+
const label2 = "L" + nextId();
|
|
2897
|
+
const start = nextId();
|
|
2898
|
+
out.push(tickSrc, `let ${result};`, `${label2}: {`, `const ${start} = c.idx;`);
|
|
2899
|
+
const values = arg.parsers.map((parser) => {
|
|
2900
|
+
const value = gen(parser, out, bindingsVar);
|
|
2901
|
+
out.push(`if (${value} === F) { c.idx = ${start}; ${result} = F; break ${label2}; }`);
|
|
2902
|
+
return value;
|
|
2903
|
+
});
|
|
2904
|
+
out.push(`${result} = ${op === "pick" ? values[arg.index] : JSON.stringify(arg.value)};`, "}");
|
|
2905
|
+
return result;
|
|
2906
|
+
}
|
|
2008
2907
|
case "prefix": {
|
|
2009
2908
|
const label2 = "L" + nextId();
|
|
2010
2909
|
const start = nextId();
|
|
@@ -2081,7 +2980,7 @@ function createCodegen(config) {
|
|
|
2081
2980
|
);
|
|
2082
2981
|
const sep = gen(sepNode, out, bindingsVar);
|
|
2083
2982
|
out.push(`if (${sep} === F) { c.idx = ${beforeSep}; break; }`, `const ${beforeItem} = c.idx;`);
|
|
2084
|
-
const item = gen(arg.rule, out, bindingsVar);
|
|
2983
|
+
const item = cold(() => gen(arg.rule, out, bindingsVar));
|
|
2085
2984
|
out.push(
|
|
2086
2985
|
`if (${item} === F) { c.idx = ${trailing ? beforeItem : beforeSep}; break; }`,
|
|
2087
2986
|
`${result}.push(${item});`,
|
|
@@ -2096,8 +2995,20 @@ function createCodegen(config) {
|
|
|
2096
2995
|
out.push(`const ${result} = ${helper(planner.plan(node), { kind: "unknownRule", name })}(c);`);
|
|
2097
2996
|
return result;
|
|
2098
2997
|
}
|
|
2099
|
-
const index = indexOfRule(name);
|
|
2100
2998
|
const parent = nextId();
|
|
2999
|
+
if (canInline(name)) {
|
|
3000
|
+
out.push(tickSrc);
|
|
3001
|
+
out.push(
|
|
3002
|
+
`const ${parent} = c.ruleDepth;`,
|
|
3003
|
+
`if (c.track) { c.ruleNames[${parent}] = ${JSON.stringify(name)}; c.ruleDepth = ${parent} + 1; }`
|
|
3004
|
+
);
|
|
3005
|
+
inlining.add(name);
|
|
3006
|
+
const inner = gen(rules[name], out, null);
|
|
3007
|
+
inlining.delete(name);
|
|
3008
|
+
out.push(`if (c.track) c.ruleDepth = ${parent};`);
|
|
3009
|
+
return inner;
|
|
3010
|
+
}
|
|
3011
|
+
const index = indexOfRule(name);
|
|
2101
3012
|
out.push(tickSrc);
|
|
2102
3013
|
if (memo) {
|
|
2103
3014
|
const key = nextId();
|
|
@@ -2118,6 +3029,26 @@ function createCodegen(config) {
|
|
|
2118
3029
|
`c.memo.set(${key}, { value: ${result}, end: c.idx });`,
|
|
2119
3030
|
"}"
|
|
2120
3031
|
);
|
|
3032
|
+
} else if (memoSlots.has(name)) {
|
|
3033
|
+
const table = nextId();
|
|
3034
|
+
const at = nextId();
|
|
3035
|
+
const end = nextId();
|
|
3036
|
+
out.push(
|
|
3037
|
+
`let ${result};`,
|
|
3038
|
+
`const ${table} = c.rm[${memoSlots.get(name)}];`,
|
|
3039
|
+
`const ${at} = c.idx;`,
|
|
3040
|
+
`const ${end} = ${table}.ends[${at}];`,
|
|
3041
|
+
`if (${end} !== 0) { c.idx = ${end} - 1; ${result} = ${table}.vals[${at}]; }`,
|
|
3042
|
+
"else {",
|
|
3043
|
+
`const ${parent} = c.ruleDepth;`,
|
|
3044
|
+
`if (c.track) { c.ruleNames[${parent}] = ${JSON.stringify(name)}; c.ruleDepth = ${parent} + 1; }`,
|
|
3045
|
+
`${result} = R[${index}](c);`,
|
|
3046
|
+
`if (c.track) c.ruleDepth = ${parent};`,
|
|
3047
|
+
`if (${result} === F) c.idx = ${at};`,
|
|
3048
|
+
`${table}.ends[${at}] = c.idx + 1;`,
|
|
3049
|
+
`${table}.vals[${at}] = ${result};`,
|
|
3050
|
+
"}"
|
|
3051
|
+
);
|
|
2121
3052
|
} else {
|
|
2122
3053
|
out.push(
|
|
2123
3054
|
`const ${parent} = c.ruleDepth;`,
|
|
@@ -2135,7 +3066,7 @@ function createCodegen(config) {
|
|
|
2135
3066
|
}
|
|
2136
3067
|
function genSeq(children, out, result) {
|
|
2137
3068
|
if (!Array.isArray(children)) throw new Error("'seq' expects an array of parsers");
|
|
2138
|
-
const idiom = recognizeSeqIdiom(children);
|
|
3069
|
+
const idiom = idioms ? recognizeSeqIdiom(children) : null;
|
|
2139
3070
|
if (idiom !== null) return gen(idiom, out, null);
|
|
2140
3071
|
let hasLabels = false;
|
|
2141
3072
|
let hasAction = false;
|
|
@@ -2171,24 +3102,53 @@ function createCodegen(config) {
|
|
|
2171
3102
|
out.push(`${result} = ${hasAction ? actionResult : "[" + collected.join(", ") + "]"};`, "}");
|
|
2172
3103
|
return result;
|
|
2173
3104
|
}
|
|
3105
|
+
function genOrderedBacktrack(list, out, bindingsVar) {
|
|
3106
|
+
const result = nextId();
|
|
3107
|
+
const label2 = "L" + nextId();
|
|
3108
|
+
const start = nextId();
|
|
3109
|
+
out.push(`let ${result};`, `${label2}: {`, `const ${start} = c.idx;`);
|
|
3110
|
+
for (const branch of list) {
|
|
3111
|
+
out.push("{");
|
|
3112
|
+
const value = gen(branch, out, bindingsVar);
|
|
3113
|
+
out.push(`if (${value} !== F) { ${result} = ${value}; break ${label2}; }`, `c.idx = ${start};`, "}");
|
|
3114
|
+
}
|
|
3115
|
+
out.push(`${result} = F;`, "}");
|
|
3116
|
+
return result;
|
|
3117
|
+
}
|
|
2174
3118
|
function genAlt(branches, out, result, bindingsVar) {
|
|
2175
3119
|
if (!Array.isArray(branches)) throw new Error("'alt' expects an array of branches");
|
|
3120
|
+
const oneOf = idioms ? recognizeAltConsumeIdiom(branches) : null;
|
|
3121
|
+
if (oneOf !== null) return gen(oneOf, out, bindingsVar);
|
|
3122
|
+
const factored = idioms ? recognizeAltPrefixIdiom(branches) : null;
|
|
3123
|
+
if (factored !== null) return gen(factored, out, bindingsVar);
|
|
2176
3124
|
const table = firsts ? buildDispatch(branches, firsts) : null;
|
|
2177
3125
|
const label2 = "L" + nextId();
|
|
2178
3126
|
if (table !== null) {
|
|
2179
3127
|
const expected = [...table.keys()];
|
|
3128
|
+
const expectedIds = tokenIds ? expected.map((t) => tokenIds.get(t)) : null;
|
|
3129
|
+
const numeric = expectedIds !== null && expectedIds.every((id) => id !== void 0);
|
|
2180
3130
|
const token = nextId();
|
|
2181
3131
|
out.push(tickSrc, `let ${result};`, `${label2}: {`, `const ${token} = c.tokens[c.idx];`);
|
|
2182
3132
|
out.push(`if (${token} === undefined) { ${expected.map((t) => `E(c, ${JSON.stringify(t)});`).join(" ")} ${result} = F; break ${label2}; }`);
|
|
2183
|
-
|
|
3133
|
+
let discriminant;
|
|
3134
|
+
if (numeric) {
|
|
3135
|
+
discriminant = nextId();
|
|
3136
|
+
out.push(`const ${discriminant} = ${token}.id !== undefined ? ${token}.id : I[${token}.type];`);
|
|
3137
|
+
out.push(`switch (${discriminant}) {`);
|
|
3138
|
+
} else {
|
|
3139
|
+
discriminant = `${token}.type`;
|
|
3140
|
+
out.push(`switch (${discriminant}) {`);
|
|
3141
|
+
}
|
|
2184
3142
|
const byBranch = /* @__PURE__ */ new Map();
|
|
2185
|
-
for (const
|
|
3143
|
+
for (const tokenName of expected) {
|
|
3144
|
+
const branch = table.get(tokenName);
|
|
2186
3145
|
if (!byBranch.has(branch)) byBranch.set(branch, []);
|
|
2187
3146
|
byBranch.get(branch).push(tokenName);
|
|
2188
3147
|
}
|
|
3148
|
+
const caseLabel = (t) => numeric ? tokenIds.get(t) : JSON.stringify(t);
|
|
2189
3149
|
for (const [branch, tokenNames] of byBranch) {
|
|
2190
|
-
out.push(tokenNames.map((t) => `case ${
|
|
2191
|
-
const value = gen(branch, out, bindingsVar);
|
|
3150
|
+
out.push(tokenNames.map((t) => `case ${caseLabel(t)}:`).join(" ") + " {");
|
|
3151
|
+
const value = Array.isArray(branch) ? genOrderedBacktrack(branch, out, bindingsVar) : gen(branch, out, bindingsVar);
|
|
2192
3152
|
out.push(`${result} = ${value}; break ${label2};`, "}");
|
|
2193
3153
|
}
|
|
2194
3154
|
out.push(
|
|
@@ -2198,16 +3158,10 @@ function createCodegen(config) {
|
|
|
2198
3158
|
);
|
|
2199
3159
|
return result;
|
|
2200
3160
|
}
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
for (const branch of branches) {
|
|
2204
|
-
out.push("{");
|
|
2205
|
-
const value = gen(branch, out, bindingsVar);
|
|
2206
|
-
out.push(`if (${value} !== F) { ${result} = ${value}; break ${label2}; }`, `c.idx = ${start};`, "}");
|
|
2207
|
-
}
|
|
2208
|
-
out.push(`${result} = F;`, "}");
|
|
2209
|
-
return result;
|
|
3161
|
+
out.push(tickSrc);
|
|
3162
|
+
return genOrderedBacktrack(branches, out, bindingsVar);
|
|
2210
3163
|
}
|
|
3164
|
+
const idByName = tokenIds ? Object.fromEntries(tokenIds) : null;
|
|
2211
3165
|
function buildRule(name) {
|
|
2212
3166
|
const out = [];
|
|
2213
3167
|
const value = gen(rules[name], out, null);
|
|
@@ -2217,8 +3171,8 @@ return ${value};
|
|
|
2217
3171
|
}`;
|
|
2218
3172
|
SOURCES[ruleIndex.get(name)] = body;
|
|
2219
3173
|
const source = `return ${body}`;
|
|
2220
|
-
const factory = new Function("F", "E", "P", "T", "R", "H", source);
|
|
2221
|
-
return factory(FAIL, expect, progressError, stepsError, RULES, HELPERS);
|
|
3174
|
+
const factory = new Function("F", "E", "P", "T", "R", "H", "I", source);
|
|
3175
|
+
return factory(FAIL, expect, progressError, stepsError, RULES, HELPERS, idByName);
|
|
2222
3176
|
}
|
|
2223
3177
|
function progressError(op, at) {
|
|
2224
3178
|
throw new Error(
|
|
@@ -2259,6 +3213,165 @@ return ${value};
|
|
|
2259
3213
|
return { compile, functionFor, sourceFor, ruleIndex, RULES, SOURCES, ORIGINS, guard };
|
|
2260
3214
|
}
|
|
2261
3215
|
|
|
3216
|
+
// src/memoplan.js
|
|
3217
|
+
function selectMemoRules(grammar, firsts, options = {}) {
|
|
3218
|
+
const ll1 = options.ll1 !== false;
|
|
3219
|
+
const names = Object.keys(grammar);
|
|
3220
|
+
const leftEdges = /* @__PURE__ */ new Map();
|
|
3221
|
+
const callEdges = /* @__PURE__ */ new Map();
|
|
3222
|
+
for (const name of names) {
|
|
3223
|
+
const left = /* @__PURE__ */ new Set();
|
|
3224
|
+
leftmostCalls(grammar[name], firsts, left);
|
|
3225
|
+
leftEdges.set(name, [...left].filter((r) => r in grammar));
|
|
3226
|
+
const calls = /* @__PURE__ */ new Set();
|
|
3227
|
+
walk(grammar[name], (node) => {
|
|
3228
|
+
if (opOf(node) !== "subrule") return;
|
|
3229
|
+
const arg = node.subrule;
|
|
3230
|
+
const target = Array.isArray(arg) ? arg[0] : arg;
|
|
3231
|
+
if (typeof target === "string" && target in grammar) calls.add(target);
|
|
3232
|
+
});
|
|
3233
|
+
callEdges.set(name, [...calls]);
|
|
3234
|
+
}
|
|
3235
|
+
const closure = (edges, from) => {
|
|
3236
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3237
|
+
const stack = [...from];
|
|
3238
|
+
while (stack.length > 0) {
|
|
3239
|
+
const next = stack.pop();
|
|
3240
|
+
if (seen.has(next)) continue;
|
|
3241
|
+
seen.add(next);
|
|
3242
|
+
for (const edge of edges.get(next) || []) stack.push(edge);
|
|
3243
|
+
}
|
|
3244
|
+
return seen;
|
|
3245
|
+
};
|
|
3246
|
+
const leftCache = /* @__PURE__ */ new Map();
|
|
3247
|
+
const leftReach = (name) => {
|
|
3248
|
+
let reach = leftCache.get(name);
|
|
3249
|
+
if (reach === void 0) {
|
|
3250
|
+
reach = closure(leftEdges, leftEdges.get(name) || []);
|
|
3251
|
+
leftCache.set(name, reach);
|
|
3252
|
+
}
|
|
3253
|
+
return reach;
|
|
3254
|
+
};
|
|
3255
|
+
const startsWith = (branch) => {
|
|
3256
|
+
const direct = /* @__PURE__ */ new Set();
|
|
3257
|
+
leftmostCalls(branch, firsts, direct);
|
|
3258
|
+
const all = /* @__PURE__ */ new Set();
|
|
3259
|
+
for (const rule of direct) {
|
|
3260
|
+
if (!(rule in grammar)) continue;
|
|
3261
|
+
all.add(rule);
|
|
3262
|
+
for (const deeper of leftReach(rule)) all.add(deeper);
|
|
3263
|
+
}
|
|
3264
|
+
return all;
|
|
3265
|
+
};
|
|
3266
|
+
const sites = [];
|
|
3267
|
+
for (const name of names) {
|
|
3268
|
+
walk(grammar[name], (node) => {
|
|
3269
|
+
if (opOf(node) !== "alt" || !Array.isArray(node.alt) || node.alt.length < 2) return;
|
|
3270
|
+
for (const group of collisionGroups(node.alt, firsts, ll1)) {
|
|
3271
|
+
const counts = /* @__PURE__ */ new Map();
|
|
3272
|
+
for (const branch of group) {
|
|
3273
|
+
for (const rule of startsWith(branch)) counts.set(rule, (counts.get(rule) || 0) + 1);
|
|
3274
|
+
}
|
|
3275
|
+
const shared = [...counts].filter(([, n]) => n >= 2).map(([rule]) => rule);
|
|
3276
|
+
const topmost = shared.filter((rule) => !shared.some((other) => other !== rule && leftReach(other).has(rule) && !leftReach(rule).has(other)));
|
|
3277
|
+
if (topmost.length > 0) sites.push({ host: name, rules: topmost });
|
|
3278
|
+
}
|
|
3279
|
+
});
|
|
3280
|
+
}
|
|
3281
|
+
const hosts = new Set(sites.map((site) => site.host));
|
|
3282
|
+
const chosen = /* @__PURE__ */ new Set();
|
|
3283
|
+
for (const { rules } of sites) {
|
|
3284
|
+
for (const rule of rules) {
|
|
3285
|
+
const inside = closure(callEdges, [rule]);
|
|
3286
|
+
inside.add(rule);
|
|
3287
|
+
for (const host of hosts) {
|
|
3288
|
+
if (inside.has(host)) {
|
|
3289
|
+
chosen.add(rule);
|
|
3290
|
+
break;
|
|
3291
|
+
}
|
|
3292
|
+
}
|
|
3293
|
+
}
|
|
3294
|
+
}
|
|
3295
|
+
const dependsOnCaller = (name) => {
|
|
3296
|
+
const reachable = closure(callEdges, [name]);
|
|
3297
|
+
reachable.add(name);
|
|
3298
|
+
for (const rule of reachable) if (usesOuterScope(grammar[rule])) return true;
|
|
3299
|
+
return false;
|
|
3300
|
+
};
|
|
3301
|
+
return names.filter((name) => chosen.has(name) && !dependsOnCaller(name));
|
|
3302
|
+
}
|
|
3303
|
+
function collisionGroups(branches, firsts, ll1) {
|
|
3304
|
+
const table = ll1 ? buildDispatch(branches, firsts) : null;
|
|
3305
|
+
if (table === null) return [branches];
|
|
3306
|
+
const groups = /* @__PURE__ */ new Set();
|
|
3307
|
+
for (const entry of table.values()) if (Array.isArray(entry) && entry.length > 1) groups.add(entry);
|
|
3308
|
+
return [...groups];
|
|
3309
|
+
}
|
|
3310
|
+
function walk(node, visit) {
|
|
3311
|
+
if (Array.isArray(node)) {
|
|
3312
|
+
for (const child of node) walk(child, visit);
|
|
3313
|
+
return;
|
|
3314
|
+
}
|
|
3315
|
+
const op = opOf(node);
|
|
3316
|
+
if (op === null) return;
|
|
3317
|
+
visit(node);
|
|
3318
|
+
const arg = node[op];
|
|
3319
|
+
switch (op) {
|
|
3320
|
+
case "seq":
|
|
3321
|
+
case "alt":
|
|
3322
|
+
if (Array.isArray(arg)) for (const child of arg) walk(child, visit);
|
|
3323
|
+
break;
|
|
3324
|
+
case "label":
|
|
3325
|
+
if (Array.isArray(arg)) walk(arg[1], visit);
|
|
3326
|
+
break;
|
|
3327
|
+
case "many":
|
|
3328
|
+
case "many1":
|
|
3329
|
+
case "option":
|
|
3330
|
+
case "flag":
|
|
3331
|
+
case "text":
|
|
3332
|
+
case "lookahead":
|
|
3333
|
+
case "negLookahead":
|
|
3334
|
+
walk(arg, visit);
|
|
3335
|
+
break;
|
|
3336
|
+
case "as":
|
|
3337
|
+
walk(Array.isArray(arg) ? arg[0] : arg && arg.parser, visit);
|
|
3338
|
+
break;
|
|
3339
|
+
case "between":
|
|
3340
|
+
if (arg) for (const part of [arg.open, arg.body, arg.close]) walk(part, visit);
|
|
3341
|
+
break;
|
|
3342
|
+
case "pick":
|
|
3343
|
+
case "yields":
|
|
3344
|
+
if (arg && Array.isArray(arg.parsers)) for (const part of arg.parsers) walk(part, visit);
|
|
3345
|
+
break;
|
|
3346
|
+
case "prefix":
|
|
3347
|
+
if (arg) {
|
|
3348
|
+
walk(arg.operator, visit);
|
|
3349
|
+
walk(arg.operand, visit);
|
|
3350
|
+
}
|
|
3351
|
+
break;
|
|
3352
|
+
case "postfix":
|
|
3353
|
+
if (arg) {
|
|
3354
|
+
walk(arg.operand, visit);
|
|
3355
|
+
walk(arg.suffix, visit);
|
|
3356
|
+
}
|
|
3357
|
+
break;
|
|
3358
|
+
case "infixLeft":
|
|
3359
|
+
case "infixRight":
|
|
3360
|
+
if (arg) {
|
|
3361
|
+
walk(arg.operand, visit);
|
|
3362
|
+
if (arg.operator !== void 0) walk(arg.operator, visit);
|
|
3363
|
+
}
|
|
3364
|
+
break;
|
|
3365
|
+
case "manySep":
|
|
3366
|
+
case "many1Sep":
|
|
3367
|
+
if (arg) {
|
|
3368
|
+
walk(arg.rule, visit);
|
|
3369
|
+
if (typeof arg.sep !== "string") walk(arg.sep, visit);
|
|
3370
|
+
}
|
|
3371
|
+
break;
|
|
3372
|
+
}
|
|
3373
|
+
}
|
|
3374
|
+
|
|
2262
3375
|
// src/parser.js
|
|
2263
3376
|
var EXECUTION_MODES = /* @__PURE__ */ new Set(["generated", "interpreted"]);
|
|
2264
3377
|
var GrammarError = class extends Error {
|
|
@@ -2274,6 +3387,9 @@ function createParser(spec, options = {}) {
|
|
|
2274
3387
|
const { tokens: tokenDefs, rules, start } = spec;
|
|
2275
3388
|
const {
|
|
2276
3389
|
memo = false,
|
|
3390
|
+
autoMemo = true,
|
|
3391
|
+
inlineRules = true,
|
|
3392
|
+
idioms = true,
|
|
2277
3393
|
strict = true,
|
|
2278
3394
|
validate = true,
|
|
2279
3395
|
maxSteps = 0,
|
|
@@ -2300,9 +3416,12 @@ function createParser(spec, options = {}) {
|
|
|
2300
3416
|
for (const [name, method] of Object.entries(extraMethods || {})) engine.addMethod(name, method);
|
|
2301
3417
|
const startRule = rules[start];
|
|
2302
3418
|
const firsts = ll1 ? analysis.firsts : null;
|
|
2303
|
-
const
|
|
3419
|
+
const tokenIds = new Map(lexer.tokenNames.map((name, id) => [name, id]));
|
|
3420
|
+
const memoRules = autoMemo && !memo && analysis.errors.length === 0 ? selectMemoRules(rules, analysis.firsts, { ll1 }) : [];
|
|
3421
|
+
const memoSlots = new Map(memoRules.map((name, slot) => [name, slot]));
|
|
3422
|
+
const planner = createPlanner({ engine, rules, firsts, memo, maxSteps, unsafeEval, tokenIds, memoSlots, idioms });
|
|
2304
3423
|
engine.__jlGrammar = { planner };
|
|
2305
|
-
const codegen = compile && unsafeEval ? createCodegen({ planner, rules, firsts, memo, maxSteps }) : null;
|
|
3424
|
+
const codegen = compile && unsafeEval ? createCodegen({ planner, rules, firsts, memo, maxSteps, tokenIds, memoSlots, inlineRules, idioms }) : null;
|
|
2306
3425
|
const compiledStart = codegen && codegen.compile(start);
|
|
2307
3426
|
if (codegen && !engine.disableInterpretedOptimization) {
|
|
2308
3427
|
for (const name of Object.keys(rules)) {
|
|
@@ -2311,7 +3430,7 @@ function createParser(spec, options = {}) {
|
|
|
2311
3430
|
}
|
|
2312
3431
|
}
|
|
2313
3432
|
function parseTokens(tokens, source, track = false) {
|
|
2314
|
-
const state = createState(tokens, rules, { memo, maxSteps, track });
|
|
3433
|
+
const state = createState(tokens, rules, { memo, maxSteps, track, memoSlots: memoRules.length });
|
|
2315
3434
|
state.firsts = firsts;
|
|
2316
3435
|
state.source = source;
|
|
2317
3436
|
state.positions = lexer.positions;
|
|
@@ -2341,6 +3460,8 @@ function createParser(spec, options = {}) {
|
|
|
2341
3460
|
planner,
|
|
2342
3461
|
codegen,
|
|
2343
3462
|
execution: mode,
|
|
3463
|
+
/** Rules memoized by `autoMemo`, for introspection. */
|
|
3464
|
+
memoRules,
|
|
2344
3465
|
compiled: Boolean(compiledStart),
|
|
2345
3466
|
/** Rules whose `alt`s resolved to O(1) LL(1) dispatch, for introspection. */
|
|
2346
3467
|
get firsts() {
|
|
@@ -2646,6 +3767,7 @@ var CONSTRUCTORS = {
|
|
|
2646
3767
|
many: { arity: [1, 1], of: ([a]) => ({ many: a }) },
|
|
2647
3768
|
many1: { arity: [1, 1], of: ([a]) => ({ many1: a }) },
|
|
2648
3769
|
option: { arity: [1, 1], of: ([a]) => ({ option: a }) },
|
|
3770
|
+
flag: { arity: [1, 1], of: ([a]) => ({ flag: a }) },
|
|
2649
3771
|
action: { arity: [1, 1], of: ([a]) => ({ action: a }) },
|
|
2650
3772
|
lookahead: { arity: [1, 1], of: ([a]) => ({ lookahead: a }) },
|
|
2651
3773
|
negLookahead: { arity: [1, 1], of: ([a]) => ({ negLookahead: a }) },
|
|
@@ -2657,6 +3779,10 @@ var CONSTRUCTORS = {
|
|
|
2657
3779
|
manySep: { arity: [2, 2], of: ([a, b]) => ({ manySep: { rule: a, sep: b } }) },
|
|
2658
3780
|
many1Sep: { arity: [2, 2], of: ([a, b]) => ({ many1Sep: { rule: a, sep: b } }) },
|
|
2659
3781
|
between: { arity: [3, 3], of: ([a, b, c]) => ({ between: { open: a, body: b, close: c } }) },
|
|
3782
|
+
// pick(1, consume(Open), subrule(Body), consume(Close)) — the result at that index.
|
|
3783
|
+
pick: { arity: [2, Infinity], of: ([index, ...parsers]) => ({ pick: { parsers, index } }) },
|
|
3784
|
+
// yields(consume(True), true) — run the parsers, give back the constant.
|
|
3785
|
+
yields: { arity: [2, Infinity], of: (args) => ({ yields: { parsers: args.slice(0, -1), value: args[args.length - 1] } }) },
|
|
2660
3786
|
infixLeft: { arity: [3, 3], of: ([a, b, c]) => ({ infixLeft: { operand: a, operator: b, combine: logicOf(c) } }) },
|
|
2661
3787
|
infixRight: { arity: [3, 3], of: ([a, b, c]) => ({ infixRight: { operand: a, operator: b, combine: logicOf(c) } }) },
|
|
2662
3788
|
prefix: { arity: [3, 3], of: ([a, b, c]) => ({ prefix: { operator: a, operand: b, combine: logicOf(c) } }) },
|
|
@@ -2677,6 +3803,355 @@ function createParserFromSource(source, options = {}) {
|
|
|
2677
3803
|
return createParser(grammar, { ...options, methods });
|
|
2678
3804
|
}
|
|
2679
3805
|
|
|
3806
|
+
// src/lexgen.js
|
|
3807
|
+
function generateLexer(defs, options = {}) {
|
|
3808
|
+
const positions = options.positions === "offset" ? "offset" : "full";
|
|
3809
|
+
const trackLines = positions === "full";
|
|
3810
|
+
const useScanners = options.scanners !== false;
|
|
3811
|
+
const useFuse = options.fuse !== false;
|
|
3812
|
+
const modeIds = /* @__PURE__ */ new Map([[DEFAULT_MODE2, 0]]);
|
|
3813
|
+
const modeNames = [DEFAULT_MODE2];
|
|
3814
|
+
const idOf = (name) => {
|
|
3815
|
+
let id = modeIds.get(name);
|
|
3816
|
+
if (id === void 0) {
|
|
3817
|
+
id = modeNames.length;
|
|
3818
|
+
modeIds.set(name, id);
|
|
3819
|
+
modeNames.push(name);
|
|
3820
|
+
}
|
|
3821
|
+
return id;
|
|
3822
|
+
};
|
|
3823
|
+
for (const def of defs) for (const mode of modesOf2(def)) idOf(mode);
|
|
3824
|
+
const compiled = defs.map((def, id) => {
|
|
3825
|
+
const flags = def.ignoreCase ? "iy" : "y";
|
|
3826
|
+
return {
|
|
3827
|
+
id,
|
|
3828
|
+
name: def.name,
|
|
3829
|
+
skip: !!def.skip,
|
|
3830
|
+
literal: def.literal,
|
|
3831
|
+
pattern: def.pattern,
|
|
3832
|
+
flags,
|
|
3833
|
+
longerAlt: def.longerAlt || null,
|
|
3834
|
+
scanner: useScanners && def.pattern ? buildScanner(def.pattern, !!def.ignoreCase) : null,
|
|
3835
|
+
modes: modesOf2(def),
|
|
3836
|
+
pushMode: def.pushMode ? idOf(def.pushMode) : -1,
|
|
3837
|
+
popMode: !!def.popMode,
|
|
3838
|
+
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)
|
|
3839
|
+
};
|
|
3840
|
+
});
|
|
3841
|
+
const byName = new Map(compiled.map((c) => [c.name, c]));
|
|
3842
|
+
const multiMode = modeNames.length > 1 || compiled.some((c) => c.popMode || c.pushMode !== -1);
|
|
3843
|
+
const regexes = [];
|
|
3844
|
+
const regexIndex = /* @__PURE__ */ new Map();
|
|
3845
|
+
const rxFor = (c) => {
|
|
3846
|
+
if (!regexIndex.has(c.id)) {
|
|
3847
|
+
regexIndex.set(c.id, regexes.length);
|
|
3848
|
+
regexes.push([c.id, c.flags]);
|
|
3849
|
+
}
|
|
3850
|
+
return (
|
|
3851
|
+
/** @type {number} */
|
|
3852
|
+
regexIndex.get(c.id)
|
|
3853
|
+
);
|
|
3854
|
+
};
|
|
3855
|
+
const fusedRuns = [];
|
|
3856
|
+
const helpers = [];
|
|
3857
|
+
const keywords = [];
|
|
3858
|
+
const lookFns = /* @__PURE__ */ new Map();
|
|
3859
|
+
const need = { digits: false, hex: false, keyword: false };
|
|
3860
|
+
function emitRegex(c) {
|
|
3861
|
+
const rx = rxFor(c);
|
|
3862
|
+
if (c.skip) {
|
|
3863
|
+
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 }`;
|
|
3864
|
+
}
|
|
3865
|
+
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 }`;
|
|
3866
|
+
}
|
|
3867
|
+
const emitted = /* @__PURE__ */ new Map();
|
|
3868
|
+
function emitDef(c) {
|
|
3869
|
+
let code = emitted.get(c.id);
|
|
3870
|
+
if (code === void 0) {
|
|
3871
|
+
code = emitDefOnce(c);
|
|
3872
|
+
emitted.set(c.id, code);
|
|
3873
|
+
}
|
|
3874
|
+
return code;
|
|
3875
|
+
}
|
|
3876
|
+
function emitDefOnce(c) {
|
|
3877
|
+
if (c.literal !== void 0) {
|
|
3878
|
+
const lit = c.literal;
|
|
3879
|
+
const test = lit.length === 1 ? `code === ${lit.charCodeAt(0)}` : `text.startsWith(${q(lit)}, offset)`;
|
|
3880
|
+
return `if (${test}) { id = ${c.id}; end = offset + ${lit.length}; matched = ${q(lit)}; break found }`;
|
|
3881
|
+
}
|
|
3882
|
+
const s = c.scanner;
|
|
3883
|
+
if (s === null) return emitRegex(c);
|
|
3884
|
+
const hit = `{ id = ${c.id}; end = e; matched = null; break found }`;
|
|
3885
|
+
switch (s.kind) {
|
|
3886
|
+
case SCANNER_KINDS.CLASS: {
|
|
3887
|
+
if (s.wide) {
|
|
3888
|
+
helpers.push(wideClassHelper(c.id, s));
|
|
3889
|
+
return `{ const e = scan${c.id}(text, offset, length); if (e === -2) { ${emitRegex(c)} } else if (e !== -1) ${hit} }`;
|
|
3890
|
+
}
|
|
3891
|
+
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 }`;
|
|
3892
|
+
}
|
|
3893
|
+
case SCANNER_KINDS.SEQ2:
|
|
3894
|
+
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 } }`;
|
|
3895
|
+
case SCANNER_KINDS.KEYWORD: {
|
|
3896
|
+
need.keyword = true;
|
|
3897
|
+
const cond = condition(s.look, "c");
|
|
3898
|
+
let look = lookFns.get(cond);
|
|
3899
|
+
if (look === void 0) {
|
|
3900
|
+
look = `look${lookFns.size}`;
|
|
3901
|
+
lookFns.set(cond, look);
|
|
3902
|
+
}
|
|
3903
|
+
keywords.push([s.lower, s.upper]);
|
|
3904
|
+
return `{ const e = scanKeyword(text, offset, length, KW[${keywords.length - 1}][0], KW[${keywords.length - 1}][1], ${look}); if (e !== -1) ${hit} }`;
|
|
3905
|
+
}
|
|
3906
|
+
case SCANNER_KINDS.LINE:
|
|
3907
|
+
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 }`;
|
|
3908
|
+
case SCANNER_KINDS.QUOTED:
|
|
3909
|
+
helpers.push(quotedHelper(c.id, s));
|
|
3910
|
+
return `{ const e = scan${c.id}(text, offset); if (e !== -1) ${hit} }`;
|
|
3911
|
+
case SCANNER_KINDS.NUMBER:
|
|
3912
|
+
need.digits = true;
|
|
3913
|
+
helpers.push(numberHelper(c.id, s));
|
|
3914
|
+
return `{ const e = scan${c.id}(text, offset, length); if (e !== -1) ${hit} }`;
|
|
3915
|
+
case SCANNER_KINDS.STRING:
|
|
3916
|
+
if (s.uni) need.hex = true;
|
|
3917
|
+
helpers.push(stringHelper(c.id, s));
|
|
3918
|
+
return `{ const e = scan${c.id}(text, offset, length); if (e !== -1) ${hit} }`;
|
|
3919
|
+
default:
|
|
3920
|
+
return emitRegex(c);
|
|
3921
|
+
}
|
|
3922
|
+
}
|
|
3923
|
+
const fusible = (c) => c.literal === void 0 && c.scanner === null && !/\\[1-9]|\\k</.test(c.pattern) && new RegExp(c.pattern + "|").exec("").length === 1;
|
|
3924
|
+
function emitList(list) {
|
|
3925
|
+
const out = [];
|
|
3926
|
+
for (let i = 0; i < list.length; ) {
|
|
3927
|
+
let j = i;
|
|
3928
|
+
while (useFuse && j < list.length && fusible(list[j]) && list[j].flags === list[i].flags) j++;
|
|
3929
|
+
if (j - i < 3) {
|
|
3930
|
+
out.push(emitDef(list[i]));
|
|
3931
|
+
i++;
|
|
3932
|
+
continue;
|
|
3933
|
+
}
|
|
3934
|
+
const run = list.slice(i, j);
|
|
3935
|
+
run.forEach(rxFor);
|
|
3936
|
+
const k = fusedRuns.length;
|
|
3937
|
+
fusedRuns.push({ flags: run[0].flags, ids: run.map((d) => d.id), rx: run.map((d) => (
|
|
3938
|
+
/** @type {number} */
|
|
3939
|
+
regexIndex.get(d.id)
|
|
3940
|
+
)) });
|
|
3941
|
+
out.push(
|
|
3942
|
+
`FX[${k}].lastIndex = offset; m = FX[${k}].exec(text)
|
|
3943
|
+
if (m !== null) {
|
|
3944
|
+
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 }
|
|
3945
|
+
// An empty match is "no match" to a lexer, so the members after it still get their turn.
|
|
3946
|
+
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 } }
|
|
3947
|
+
}`
|
|
3948
|
+
);
|
|
3949
|
+
i = j;
|
|
3950
|
+
}
|
|
3951
|
+
return out.join("\n ");
|
|
3952
|
+
}
|
|
3953
|
+
const lists = [{ list: (
|
|
3954
|
+
/** @type {any[]} */
|
|
3955
|
+
[]
|
|
3956
|
+
) }];
|
|
3957
|
+
const listIds = /* @__PURE__ */ new Map([["", 0]]);
|
|
3958
|
+
const listFor = (list) => {
|
|
3959
|
+
const key = list.map((c) => c.id).join(",");
|
|
3960
|
+
let id = listIds.get(key);
|
|
3961
|
+
if (id === void 0) {
|
|
3962
|
+
id = lists.length;
|
|
3963
|
+
listIds.set(key, id);
|
|
3964
|
+
lists.push({ list });
|
|
3965
|
+
}
|
|
3966
|
+
return id;
|
|
3967
|
+
};
|
|
3968
|
+
const WIDTH = 129;
|
|
3969
|
+
const table = [];
|
|
3970
|
+
for (const name of modeNames) {
|
|
3971
|
+
const members = compiled.filter((c) => c.modes.includes(name));
|
|
3972
|
+
if (members.length === 0) throw new Error(`generateLexer: mode '${name}' has no tokens`);
|
|
3973
|
+
const dispatch = members.some((c) => c.firstChars !== null);
|
|
3974
|
+
for (let code = 0; code < 128; code++) {
|
|
3975
|
+
table.push(listFor(dispatch ? members.filter((c) => c.firstChars === null || c.firstChars.has(code)) : members));
|
|
3976
|
+
}
|
|
3977
|
+
table.push(listFor(dispatch ? members.filter((c) => c.firstChars === null || [...c.firstChars].some((x) => x >= 128)) : members));
|
|
3978
|
+
}
|
|
3979
|
+
if (lists.length > 6e4) throw new Error("generateLexer: too many distinct candidate lists");
|
|
3980
|
+
const dispatchTable = String.fromCharCode(...table.map((id) => 256 + id));
|
|
3981
|
+
const listCases = lists.slice(1).map(({ list }, i) => `case ${i + 1}: {
|
|
3982
|
+
${emitList(list)}
|
|
3983
|
+
break
|
|
3984
|
+
}`);
|
|
3985
|
+
const longer = compiled.filter((c) => c.longerAlt);
|
|
3986
|
+
const longerCode = longer.length === 0 ? "" : `
|
|
3987
|
+
switch (id) {
|
|
3988
|
+
${longer.map((c) => {
|
|
3989
|
+
const alt2 = (
|
|
3990
|
+
/** @type {any} */
|
|
3991
|
+
byName.get(
|
|
3992
|
+
/** @type {string} */
|
|
3993
|
+
c.longerAlt
|
|
3994
|
+
)
|
|
3995
|
+
);
|
|
3996
|
+
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)`;
|
|
3997
|
+
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 }`;
|
|
3998
|
+
}).join("\n")}
|
|
3999
|
+
}`;
|
|
4000
|
+
const transitions = compiled.filter((c) => c.popMode || c.pushMode !== -1);
|
|
4001
|
+
const describe = trackLines ? "'line ' + line + ' column ' + (offset - lineStart + 1)" : "'offset ' + offset";
|
|
4002
|
+
const transitionCode = transitions.length === 0 ? "" : `
|
|
4003
|
+
switch (id) {
|
|
4004
|
+
${transitions.map((c) => c.popMode ? ` case ${c.id}:
|
|
4005
|
+
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"} })
|
|
4006
|
+
mode = stack[--depth]
|
|
4007
|
+
break` : ` case ${c.id}: stack[depth++] = mode; mode = ${c.pushMode}; break`).join("\n")}
|
|
4008
|
+
}`;
|
|
4009
|
+
const where = trackLines ? "'line ' + line + ' column ' + column" : "'offset ' + offset";
|
|
4010
|
+
const inMode = modeNames.length > 1 ? " + ' (in lexer mode \\'' + MODE_NAMES[mode] + '\\')'" : "";
|
|
4011
|
+
return `(() => {
|
|
4012
|
+
const NAMES = tokens.map((t) => t.name)
|
|
4013
|
+
const SKIP = tokens.map((t) => (t.skip ? 1 : 0))
|
|
4014
|
+
const DISPATCH = ${q(dispatchTable)}
|
|
4015
|
+
${modeNames.length > 1 ? `const MODE_NAMES = ${q(modeNames)}` : ""}
|
|
4016
|
+
// Patterns are read from the token table above rather than repeated here.
|
|
4017
|
+
const RX = ${q(regexes)}.map(([i, flags]) => new RegExp(tokens[i].pattern, flags))
|
|
4018
|
+
${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))
|
|
4019
|
+
const FIDS = ${q(fusedRuns.map((f) => f.ids))}
|
|
4020
|
+
const FRX = ${q(fusedRuns.map((f) => f.rx))}` : ""}
|
|
4021
|
+
${need.digits ? "const skipDigits = (text, i, length) => { while (i < length) { const c = text.charCodeAt(i); if (c < 48 || c > 57) break; i++ } return i }" : ""}
|
|
4022
|
+
${need.hex ? "const isHex = (c) => (c >= 48 && c <= 57) || (c >= 65 && c <= 70) || (c >= 97 && c <= 102)" : ""}
|
|
4023
|
+
${need.keyword ? `const KW = ${q(keywords)}
|
|
4024
|
+
${[...lookFns].map(([cond, name]) => `const ${name} = (c) => ${cond}`).join("\n ")}
|
|
4025
|
+
${KEYWORD_HELPER}` : ""}
|
|
4026
|
+
${helpers.join("\n")}
|
|
4027
|
+
|
|
4028
|
+
function tokenize (text) {
|
|
4029
|
+
if (typeof text !== 'string') throw new TypeError('tokenize: expected a string')
|
|
4030
|
+
const tokens_ = []
|
|
4031
|
+
let offset = 0
|
|
4032
|
+
let line = 1
|
|
4033
|
+
let lineStart = 0
|
|
4034
|
+
const length = text.length
|
|
4035
|
+
${multiMode ? "let mode = 0\n const stack = []\n let depth = 0" : ""}
|
|
4036
|
+
let m = null
|
|
4037
|
+
while (offset < length) {
|
|
4038
|
+
const code = text.charCodeAt(offset)
|
|
4039
|
+
let id = -1
|
|
4040
|
+
let end = -1
|
|
4041
|
+
let matched = null
|
|
4042
|
+
found: {
|
|
4043
|
+
switch (DISPATCH.charCodeAt(${multiMode ? `mode * ${WIDTH} + ` : ""}(code < 128 ? code : 128)) - 256) {
|
|
4044
|
+
${listCases.join("\n ")}
|
|
4045
|
+
}
|
|
4046
|
+
}
|
|
4047
|
+
if (id === -1) {
|
|
4048
|
+
const column = offset - lineStart + 1
|
|
4049
|
+
throw new LexError('Unexpected character \\'' + text[offset] + '\\' at ' + ${where}${inMode}, { offset, line: ${trackLines ? "line" : "0"}, col: ${trackLines ? "column" : "0"} })
|
|
4050
|
+
}${longerCode}
|
|
4051
|
+
if (SKIP[id] === 0) {
|
|
4052
|
+
const image = matched !== null ? matched : text.slice(offset, end)
|
|
4053
|
+
tokens_.push(${trackLines ? "{ type: NAMES[id], image, start: offset, end, line, col: offset - lineStart + 1 }" : "{ type: NAMES[id], id, image, start: offset, end }"})
|
|
4054
|
+
}
|
|
4055
|
+
${trackLines ? "for (let j = offset; j < end; j++) { if (text.charCodeAt(j) !== 10) continue; line++; lineStart = j + 1 }" : ""}${transitionCode}
|
|
4056
|
+
offset = end
|
|
4057
|
+
}
|
|
4058
|
+
return tokens_
|
|
4059
|
+
}
|
|
4060
|
+
return { tokenize, tokenNames: NAMES, positions: ${q(positions)}, modeNames: ${q(modeNames)} }
|
|
4061
|
+
})()`;
|
|
4062
|
+
}
|
|
4063
|
+
var DEFAULT_MODE2 = "default";
|
|
4064
|
+
var q = JSON.stringify;
|
|
4065
|
+
function modesOf2(def) {
|
|
4066
|
+
if (Array.isArray(def.modes) && def.modes.length > 0) return def.modes;
|
|
4067
|
+
if (def.mode) return [def.mode];
|
|
4068
|
+
return [DEFAULT_MODE2];
|
|
4069
|
+
}
|
|
4070
|
+
function condition(table, v) {
|
|
4071
|
+
const parts = [];
|
|
4072
|
+
for (let i = 0; i < 128; ) {
|
|
4073
|
+
if (table[i] !== 1) {
|
|
4074
|
+
i++;
|
|
4075
|
+
continue;
|
|
4076
|
+
}
|
|
4077
|
+
let j = i;
|
|
4078
|
+
while (j + 1 < 128 && table[j + 1] === 1) j++;
|
|
4079
|
+
parts.push(i === j ? `${v} === ${i}` : `(${v} >= ${i} && ${v} <= ${j})`);
|
|
4080
|
+
i = j + 1;
|
|
4081
|
+
}
|
|
4082
|
+
return parts.length > 0 ? parts.join(" || ") : "false";
|
|
4083
|
+
}
|
|
4084
|
+
var KEYWORD_HELPER = `function scanKeyword (text, offset, length, lower, upper, look) {
|
|
4085
|
+
const n = lower.length
|
|
4086
|
+
if (offset + n > length) return -1
|
|
4087
|
+
for (let i = 0; i < n; i++) {
|
|
4088
|
+
const c = text.charCodeAt(offset + i)
|
|
4089
|
+
if (c !== lower[i] && c !== upper[i]) return -1
|
|
4090
|
+
}
|
|
4091
|
+
const end = offset + n
|
|
4092
|
+
if (end < length && look(text.charCodeAt(end))) return -1
|
|
4093
|
+
return end
|
|
4094
|
+
}`;
|
|
4095
|
+
function wideClassHelper(id, s) {
|
|
4096
|
+
return `function scan${id} (text, offset, length) {
|
|
4097
|
+
const code = text.charCodeAt(offset)
|
|
4098
|
+
if (code >= 128) return -2
|
|
4099
|
+
if (!(${condition(s.first, "code")})) return -1
|
|
4100
|
+
let j = offset + 1
|
|
4101
|
+
while (j < length) {
|
|
4102
|
+
const c = text.charCodeAt(j)
|
|
4103
|
+
if (c >= 128) return -2
|
|
4104
|
+
if (!(${condition(s.rest, "c")})) break
|
|
4105
|
+
j++
|
|
4106
|
+
}
|
|
4107
|
+
return j
|
|
4108
|
+
}`;
|
|
4109
|
+
}
|
|
4110
|
+
function quotedHelper(id, s) {
|
|
4111
|
+
return `function scan${id} (text, offset) {
|
|
4112
|
+
if (text.charCodeAt(offset) !== ${s.quote}) return -1
|
|
4113
|
+
let fallback = -1
|
|
4114
|
+
let j = offset + 1
|
|
4115
|
+
for (;;) {
|
|
4116
|
+
j = text.indexOf(${q(String.fromCharCode(s.quote))}, j)
|
|
4117
|
+
if (j === -1) return fallback
|
|
4118
|
+
if (text.charCodeAt(j + 1) !== ${s.quote}) return j + 1
|
|
4119
|
+
fallback = j + 1
|
|
4120
|
+
j += 2
|
|
4121
|
+
}
|
|
4122
|
+
}`;
|
|
4123
|
+
}
|
|
4124
|
+
function numberHelper(id, s) {
|
|
4125
|
+
return `function scan${id} (text, offset, length) {
|
|
4126
|
+
let j = offset
|
|
4127
|
+
${s.sign ? "if (text.charCodeAt(j) === 45) j++" : ""}
|
|
4128
|
+
const c = text.charCodeAt(j)
|
|
4129
|
+
${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)"}
|
|
4130
|
+
${s.frac ? "if (text.charCodeAt(j) === 46) { const k = skipDigits(text, j + 1, length); if (k > j + 1) j = k }" : ""}
|
|
4131
|
+
${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 }" : ""}
|
|
4132
|
+
return j
|
|
4133
|
+
}`;
|
|
4134
|
+
}
|
|
4135
|
+
function stringHelper(id, s) {
|
|
4136
|
+
return `function scan${id} (text, offset, length) {
|
|
4137
|
+
if (text.charCodeAt(offset) !== ${s.quote}) return -1
|
|
4138
|
+
let j = offset + 1
|
|
4139
|
+
for (;;) {
|
|
4140
|
+
if (j >= length) return -1
|
|
4141
|
+
const c = text.charCodeAt(j)
|
|
4142
|
+
if (c === ${s.quote}) return j + 1
|
|
4143
|
+
if (c === 92) {
|
|
4144
|
+
const d = text.charCodeAt(j + 1)
|
|
4145
|
+
${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
|
|
4146
|
+
${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`}
|
|
4147
|
+
continue
|
|
4148
|
+
}
|
|
4149
|
+
${s.ctrl ? "if (c < 32) return -1" : ""}
|
|
4150
|
+
j++
|
|
4151
|
+
}
|
|
4152
|
+
}`;
|
|
4153
|
+
}
|
|
4154
|
+
|
|
2680
4155
|
// src/emit.js
|
|
2681
4156
|
import { Compiler, LogicEngine as LogicEngine2 } from "json-logic-engine";
|
|
2682
4157
|
var UNSUPPORTED = /\b(coerceArray|precoerceNumber|compareCheck|assertAllowedDepth|assertSize|asyncIterators|notTraversed)\b|(?<!engine\.)\bmethods\[|engine\.(?!methods\b)/;
|
|
@@ -2725,11 +4200,21 @@ function emitModule(spec, options = {}) {
|
|
|
2725
4200
|
const { tokens: tokenDefs, rules, start } = spec;
|
|
2726
4201
|
const {
|
|
2727
4202
|
memo = false,
|
|
4203
|
+
autoMemo = true,
|
|
4204
|
+
// Off here, on in `createParser`. Inlining a precedence chain is worth 2-3x on a bare chain but only
|
|
4205
|
+
// 1.02-1.04x on the formula and SQL examples, and it makes the file bigger: the formula module grows
|
|
4206
|
+
// 32% minified (38 to 50 KB) even after the second copy of each operand is kept as a call. A parser
|
|
4207
|
+
// built at run time pays nothing for that; a file someone ships does.
|
|
4208
|
+
inlineRules = false,
|
|
4209
|
+
idioms = true,
|
|
2728
4210
|
strict = true,
|
|
2729
4211
|
ll1 = true,
|
|
2730
4212
|
maxSteps = 0,
|
|
2731
4213
|
positions = "full",
|
|
2732
4214
|
execution = "generated",
|
|
4215
|
+
lexer: lexerMode = "generated",
|
|
4216
|
+
scanners = true,
|
|
4217
|
+
fuse = true,
|
|
2733
4218
|
format = "esm",
|
|
2734
4219
|
validate = true,
|
|
2735
4220
|
moduleName = spec.name || "parser",
|
|
@@ -2738,6 +4223,7 @@ function emitModule(spec, options = {}) {
|
|
|
2738
4223
|
methods: semanticMethods
|
|
2739
4224
|
} = options;
|
|
2740
4225
|
if (format !== "esm" && format !== "cjs") throw new Error(`emitModule: format must be 'esm' or 'cjs', got ${quote(format)}`);
|
|
4226
|
+
if (lexerMode !== "generated" && lexerMode !== "runtime") throw new Error(`emitModule: lexer must be 'generated' or 'runtime', got ${quote(lexerMode)}`);
|
|
2741
4227
|
const methodsBlock = options.methodsBlock !== void 0 ? options.methodsBlock : spec.methodsBlock;
|
|
2742
4228
|
const block = methodsBlock ? emitMethodsBlock(methodsBlock, format) : null;
|
|
2743
4229
|
if (!start || !(start in rules)) throw new Error(`emitModule: start rule '${start}' is not defined in rules`);
|
|
@@ -2745,12 +4231,15 @@ function emitModule(spec, options = {}) {
|
|
|
2745
4231
|
const analysis = analyze(rules, { tokenNames: lexer.tokenNames });
|
|
2746
4232
|
if (validate && analysis.errors.length > 0) throw new GrammarError(analysis.errors);
|
|
2747
4233
|
const firsts = ll1 ? analysis.firsts : null;
|
|
4234
|
+
const tokenIds = new Map(lexer.tokenNames.map((name, id) => [name, id]));
|
|
2748
4235
|
const engine = { build: () => {
|
|
2749
4236
|
throw new Error("not compiled during emit");
|
|
2750
4237
|
}, run: () => {
|
|
2751
4238
|
} };
|
|
2752
|
-
const
|
|
2753
|
-
const
|
|
4239
|
+
const memoRules = autoMemo && !memo && analysis.errors.length === 0 ? selectMemoRules(rules, analysis.firsts, { ll1 }) : [];
|
|
4240
|
+
const memoSlots = new Map(memoRules.map((name, slot) => [name, slot]));
|
|
4241
|
+
const planner = createPlanner({ engine, rules, firsts, memo, maxSteps, unsafeEval: false, tokenIds, memoSlots, idioms });
|
|
4242
|
+
const codegen = createCodegen({ planner, rules, firsts, memo, maxSteps, tokenIds, memoSlots, inlineRules, idioms });
|
|
2754
4243
|
codegen.compile(start);
|
|
2755
4244
|
const reached = [...codegen.ruleIndex.entries()].sort((a, b) => a[1] - b[1]);
|
|
2756
4245
|
const unsupported = codegen.ORIGINS.map((origin, index) => ({ origin, index })).filter(({ origin }) => origin.kind === "unknownRule");
|
|
@@ -2791,7 +4280,8 @@ R[${index}] = ${codegen.SOURCES[index]}`
|
|
|
2791
4280
|
).join("\n\n");
|
|
2792
4281
|
const interpretedActions = execution === "interpreted";
|
|
2793
4282
|
const cjs = format === "cjs";
|
|
2794
|
-
const
|
|
4283
|
+
const generatedLexer = lexerMode === "generated";
|
|
4284
|
+
const runtimeImports = ["FAIL as F", "expect as E", "createState", "buildParseError", generatedLexer ? "LexError" : "createLexer"];
|
|
2795
4285
|
if (needsEngine) runtimeImports.push("createValueMethods");
|
|
2796
4286
|
const importRuntime = cjs ? `const { ${runtimeImports.map((b) => b.replace(" as ", ": ")).join(", ")} } = require(${quote(runtimeSpecifier)})` : `import { ${runtimeImports.join(", ")} } from ${quote(runtimeSpecifier)}`;
|
|
2797
4287
|
const importEngine = engineFree ? "" : cjs ? `const { LogicEngine } = require(${quote(engineSpecifier)})
|
|
@@ -2810,7 +4300,7 @@ export default { ${PUBLIC.join(", ")} }`;
|
|
|
2810
4300
|
//
|
|
2811
4301
|
// The grammar was analyzed when this file was written: FIRST sets, left-recursion
|
|
2812
4302
|
// checks and rule specialization all happened then. Importing it does no grammar
|
|
2813
|
-
// work and generates no code.
|
|
4303
|
+
// work and generates no code.${generatedLexer ? "\n// The lexer was written out as source too, so there is no lexer engine to load." : ""}
|
|
2814
4304
|
${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."}
|
|
2815
4305
|
|
|
2816
4306
|
${importRuntime}
|
|
@@ -2820,7 +4310,7 @@ const tokens = ${literal(tokenDefs)}
|
|
|
2820
4310
|
|
|
2821
4311
|
const start = ${quote(start)}
|
|
2822
4312
|
|
|
2823
|
-
const lexer = createLexer(tokens, { positions: ${quote(positions)} })
|
|
4313
|
+
const lexer = ${generatedLexer ? generateLexer(tokenDefs, { positions, scanners, fuse }) : `createLexer(tokens, { positions: ${quote(positions)}, scanners: ${scanners}, fuse: ${fuse} })`}
|
|
2824
4314
|
const tokenize = lexer.tokenize
|
|
2825
4315
|
|
|
2826
4316
|
${engineFree ? `// Every semantic action compiled to source, so the engine reduced to a table
|
|
@@ -2893,6 +4383,14 @@ const H = [
|
|
|
2893
4383
|
${helpers.map((source, index) => ` /* ${index} */ ${source}`).join(",\n")}
|
|
2894
4384
|
]
|
|
2895
4385
|
|
|
4386
|
+
/**
|
|
4387
|
+
* Name -> id, the same numbering \`lexer\` assigns. A generated \`alt\`'s switch
|
|
4388
|
+
* dispatches on a token's id when it has one; this is only read as a fallback,
|
|
4389
|
+
* for a hand-built token from \`parseTokens\` that bypassed this file's own
|
|
4390
|
+
* lexer and so has no id of its own.
|
|
4391
|
+
*/
|
|
4392
|
+
const I = ${literal(Object.fromEntries(tokenIds))}
|
|
4393
|
+
|
|
2896
4394
|
${ruleSources}
|
|
2897
4395
|
|
|
2898
4396
|
const RULES = ${literal(Object.fromEntries(reached.map(([name, index]) => [name, index])))}
|
|
@@ -2903,7 +4401,7 @@ const RULES = ${literal(Object.fromEntries(reached.map(([name, index]) => [name,
|
|
|
2903
4401
|
* @param {string} [source]
|
|
2904
4402
|
*/
|
|
2905
4403
|
function parseTokens (tokenList, source, track = false) {
|
|
2906
|
-
const state = createState(tokenList, {}, { memo: ${memo}, maxSteps: ${maxSteps}, track })
|
|
4404
|
+
const state = createState(tokenList, {}, { memo: ${memo}, maxSteps: ${maxSteps}, track, memoSlots: ${memoRules.length} })
|
|
2907
4405
|
state.source = source
|
|
2908
4406
|
state.positions = ${quote(positions)}
|
|
2909
4407
|
state.ruleNames[0] = start
|