grammatik 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -0
- package/bin/grammatik.js +8 -0
- package/dist/cjs/index.js +1471 -104
- package/dist/cjs/runtime.js +635 -25
- package/dist/esm/index.js +1471 -104
- package/dist/esm/runtime.js +635 -25
- package/dist/types/index.d.ts +36 -0
- package/dist/types/runtime.d.ts +4 -3
- package/package.json +1 -1
- package/src/analyze.js +114 -4
- package/src/codegen.js +147 -11
- package/src/definition.js +5 -0
- package/src/emit.js +29 -7
- package/src/lexer.d.ts +43 -2
- package/src/lexer.js +535 -28
- package/src/lexgen.js +451 -0
- package/src/memoplan.js +220 -0
- package/src/methods.js +1 -1
- package/src/parser.js +31 -3
- package/src/plan.js +88 -26
- package/src/runtime.js +8 -6
- package/src/scan.js +422 -0
- package/src/state.js +33 -1
package/dist/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();
|
|
@@ -381,9 +741,15 @@ function createLexer(defs, options = {}) {
|
|
|
381
741
|
literal: def.literal,
|
|
382
742
|
longerAlt: def.longerAlt || null,
|
|
383
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,
|
|
384
748
|
modes: modesOf(def),
|
|
385
749
|
pushMode: def.pushMode ? idOf(def.pushMode) : -1,
|
|
386
|
-
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
|
|
387
753
|
};
|
|
388
754
|
});
|
|
389
755
|
const byName = new Map(compiled.map((c) => [c.name, c]));
|
|
@@ -400,14 +766,47 @@ function createLexer(defs, options = {}) {
|
|
|
400
766
|
for (const c of compiled) {
|
|
401
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);
|
|
402
768
|
}
|
|
403
|
-
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) => {
|
|
404
802
|
const members = compiled.filter((c) => c.modes.includes(name));
|
|
405
803
|
const alwaysTry = members.filter((c) => c.firstChars === null);
|
|
406
|
-
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)));
|
|
407
805
|
const ascii = new Array(128);
|
|
408
806
|
for (let code = 0; code < 128; code++) ascii[code] = candidatesFor(code);
|
|
409
807
|
return {
|
|
410
808
|
name,
|
|
809
|
+
id,
|
|
411
810
|
members,
|
|
412
811
|
ascii,
|
|
413
812
|
wide: /* @__PURE__ */ new Map(),
|
|
@@ -431,6 +830,30 @@ function createLexer(defs, options = {}) {
|
|
|
431
830
|
const multiMode = modes.length > 1 || compiled.some((c) => c.popMode || c.pushMode !== -1);
|
|
432
831
|
const namesModes = modes.length > 1;
|
|
433
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
|
+
}
|
|
434
857
|
function longerAltEnd(def, text, offset) {
|
|
435
858
|
const alt2 = def.longerAltDef;
|
|
436
859
|
if (alt2.literal !== void 0) return text.startsWith(alt2.literal, offset) ? offset + alt2.literal.length : -1;
|
|
@@ -450,19 +873,30 @@ function createLexer(defs, options = {}) {
|
|
|
450
873
|
outer: while (offset < length) {
|
|
451
874
|
const applicable = mode.dispatch ? candidates(mode, text.charCodeAt(offset)) : mode.members;
|
|
452
875
|
for (let i = 0; i < applicable.length; i++) {
|
|
453
|
-
|
|
876
|
+
let def = applicable[i];
|
|
454
877
|
let end = -1;
|
|
455
878
|
let matched = null;
|
|
456
|
-
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) {
|
|
457
885
|
if (text.startsWith(def.literal, offset)) end = offset + def.literal.length;
|
|
458
|
-
} else if (def.skip) {
|
|
459
|
-
def.regex.lastIndex = offset;
|
|
460
|
-
if (def.regex.test(text) && def.regex.lastIndex > offset) end = def.regex.lastIndex;
|
|
461
886
|
} else {
|
|
462
|
-
def.
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
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
|
+
}
|
|
466
900
|
}
|
|
467
901
|
if (end === -1) continue;
|
|
468
902
|
let winner = def;
|
|
@@ -511,62 +945,238 @@ function createLexer(defs, options = {}) {
|
|
|
511
945
|
}
|
|
512
946
|
const inMode = (mode) => namesModes ? ` (in lexer mode '${mode.name}')` : "";
|
|
513
947
|
const describe = (offset, line, lineStart) => trackLines ? `line ${line} column ${offset - lineStart + 1}` : `offset ${offset}`;
|
|
514
|
-
return { tokenize, tokenNames, positions, modeNames };
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
idx: 0,
|
|
522
|
-
maxIdx: 0,
|
|
523
|
-
expected: [],
|
|
524
|
-
grammar,
|
|
525
|
-
memo: options.memo ? /* @__PURE__ */ new Map() : null,
|
|
526
|
-
// Entering a rule is one array store rather than an object allocation, and
|
|
527
|
-
// the stack is snapshotted only on a furthest failure — by the time an error
|
|
528
|
-
// is built it would otherwise have unwound to just the start rule.
|
|
529
|
-
ruleNames: [],
|
|
530
|
-
ruleDepth: 0,
|
|
531
|
-
failureStack: null,
|
|
532
|
-
track: options.track === true,
|
|
533
|
-
steps: 0,
|
|
534
|
-
maxSteps: options.maxSteps || 0
|
|
535
|
-
});
|
|
536
|
-
function expect(state, expectation) {
|
|
537
|
-
if (state.idx > state.maxIdx) {
|
|
538
|
-
state.maxIdx = state.idx;
|
|
539
|
-
if (!state.track) return;
|
|
540
|
-
state.expected = [];
|
|
541
|
-
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;
|
|
542
955
|
}
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
/**
|
|
548
|
-
* @param {string} message
|
|
549
|
-
* @param {{ token: import('./lexer.js').Token | null, expected: string[], ruleStack: string[] }} details
|
|
550
|
-
*/
|
|
551
|
-
constructor(message, details) {
|
|
552
|
-
super(message);
|
|
553
|
-
this.name = "ParseError";
|
|
554
|
-
this.token = details.token;
|
|
555
|
-
this.expected = details.expected;
|
|
556
|
-
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;
|
|
557
960
|
}
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
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 };
|
|
570
1180
|
}
|
|
571
1181
|
|
|
572
1182
|
// src/analyze.js
|
|
@@ -599,6 +1209,7 @@ var PARSER_OPS = /* @__PURE__ */ new Set([
|
|
|
599
1209
|
"many",
|
|
600
1210
|
"many1",
|
|
601
1211
|
"option",
|
|
1212
|
+
"flag",
|
|
602
1213
|
"manySep",
|
|
603
1214
|
"many1Sep",
|
|
604
1215
|
"infixLeft",
|
|
@@ -606,6 +1217,8 @@ var PARSER_OPS = /* @__PURE__ */ new Set([
|
|
|
606
1217
|
"postfix",
|
|
607
1218
|
"prefix",
|
|
608
1219
|
"between",
|
|
1220
|
+
"pick",
|
|
1221
|
+
"yields",
|
|
609
1222
|
"as",
|
|
610
1223
|
"oneOf",
|
|
611
1224
|
"eof",
|
|
@@ -659,7 +1272,8 @@ function firstOf(node, ruleFirsts) {
|
|
|
659
1272
|
return result;
|
|
660
1273
|
}
|
|
661
1274
|
case "many":
|
|
662
|
-
case "option":
|
|
1275
|
+
case "option":
|
|
1276
|
+
case "flag": {
|
|
663
1277
|
const inner = firstOf(arg, ruleFirsts);
|
|
664
1278
|
return { tokens: inner.tokens, nullable: true, unknown: inner.unknown };
|
|
665
1279
|
}
|
|
@@ -682,6 +1296,9 @@ function firstOf(node, ruleFirsts) {
|
|
|
682
1296
|
return firstOf(Array.isArray(arg) ? arg[0] : arg && arg.parser, ruleFirsts);
|
|
683
1297
|
case "between":
|
|
684
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);
|
|
685
1302
|
case "prefix": {
|
|
686
1303
|
const ops = firstOf(arg && arg.operator, ruleFirsts);
|
|
687
1304
|
const inner = firstOf(arg && arg.operand, ruleFirsts);
|
|
@@ -745,7 +1362,36 @@ function recognizeSeqIdiom(children) {
|
|
|
745
1362
|
if (rewritten !== BAIL) return { as: [parser, rewritten] };
|
|
746
1363
|
}
|
|
747
1364
|
}
|
|
748
|
-
|
|
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
|
+
}
|
|
1379
|
+
return null;
|
|
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;
|
|
749
1395
|
}
|
|
750
1396
|
function isPlainParser(node) {
|
|
751
1397
|
const op = opOf(node);
|
|
@@ -792,6 +1438,18 @@ function recognizeAltPrefixIdiom(branches) {
|
|
|
792
1438
|
]
|
|
793
1439
|
};
|
|
794
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
|
+
}
|
|
795
1453
|
var BAIL = /* @__PURE__ */ Symbol("grammatik.bail");
|
|
796
1454
|
function rebaseOnLabel(logic, name) {
|
|
797
1455
|
if (Array.isArray(logic)) {
|
|
@@ -858,6 +1516,7 @@ function leftmostCalls(node, ruleFirsts, out) {
|
|
|
858
1516
|
case "many":
|
|
859
1517
|
case "many1":
|
|
860
1518
|
case "option":
|
|
1519
|
+
case "flag":
|
|
861
1520
|
case "lookahead":
|
|
862
1521
|
case "negLookahead":
|
|
863
1522
|
leftmostCalls(arg, ruleFirsts, out);
|
|
@@ -868,6 +1527,10 @@ function leftmostCalls(node, ruleFirsts, out) {
|
|
|
868
1527
|
case "between":
|
|
869
1528
|
if (arg) leftmostCalls({ seq: [arg.open, arg.body, arg.close] }, ruleFirsts, out);
|
|
870
1529
|
break;
|
|
1530
|
+
case "pick":
|
|
1531
|
+
case "yields":
|
|
1532
|
+
if (arg && Array.isArray(arg.parsers)) leftmostCalls({ seq: arg.parsers }, ruleFirsts, out);
|
|
1533
|
+
break;
|
|
871
1534
|
case "prefix":
|
|
872
1535
|
if (arg) {
|
|
873
1536
|
leftmostCalls(arg.operator, ruleFirsts, out);
|
|
@@ -934,6 +1597,7 @@ function validateNode(node, grammar, tokenNames, errors, path) {
|
|
|
934
1597
|
case "many":
|
|
935
1598
|
case "many1":
|
|
936
1599
|
case "option":
|
|
1600
|
+
case "flag":
|
|
937
1601
|
case "text":
|
|
938
1602
|
case "lookahead":
|
|
939
1603
|
case "negLookahead":
|
|
@@ -955,6 +1619,26 @@ function validateNode(node, grammar, tokenNames, errors, path) {
|
|
|
955
1619
|
}
|
|
956
1620
|
break;
|
|
957
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
|
+
}
|
|
958
1642
|
case "prefix": {
|
|
959
1643
|
if (!arg || typeof arg !== "object" || !("operator" in arg) || !("operand" in arg) || !("combine" in arg)) {
|
|
960
1644
|
errors.push(`${path}: 'prefix' expects { operator, operand, combine }`);
|
|
@@ -1081,20 +1765,6 @@ function declareSync(fn) {
|
|
|
1081
1765
|
fn[SYNC] = true;
|
|
1082
1766
|
return fn;
|
|
1083
1767
|
}
|
|
1084
|
-
function usesOuterScope(logic) {
|
|
1085
|
-
if (Array.isArray(logic)) return logic.some(usesOuterScope);
|
|
1086
|
-
if (!logic || typeof logic !== "object") return false;
|
|
1087
|
-
for (const key of Object.keys(logic)) {
|
|
1088
|
-
const arg = logic[key];
|
|
1089
|
-
if (key === "val" && Array.isArray(arg) && Array.isArray(arg[0])) return true;
|
|
1090
|
-
if (key === "var") {
|
|
1091
|
-
if (typeof arg === "string" && arg.includes("../")) return true;
|
|
1092
|
-
if (Array.isArray(arg) && typeof arg[0] === "string" && arg[0].includes("../")) return true;
|
|
1093
|
-
}
|
|
1094
|
-
if (usesOuterScope(arg)) return true;
|
|
1095
|
-
}
|
|
1096
|
-
return false;
|
|
1097
|
-
}
|
|
1098
1768
|
function mayReturnContext(logic) {
|
|
1099
1769
|
if (Array.isArray(logic)) return logic.some(mayReturnContext);
|
|
1100
1770
|
if (!logic || typeof logic !== "object") return false;
|
|
@@ -1107,7 +1777,7 @@ function mayReturnContext(logic) {
|
|
|
1107
1777
|
return false;
|
|
1108
1778
|
}
|
|
1109
1779
|
function createPlanner(config) {
|
|
1110
|
-
const { engine, rules, firsts, memo, maxSteps, unsafeEval = true, tokenIds = null } = config;
|
|
1780
|
+
const { engine, rules, firsts, memo, maxSteps, unsafeEval = true, tokenIds = null, memoSlots = /* @__PURE__ */ new Map(), idioms = true } = config;
|
|
1111
1781
|
const slots = /* @__PURE__ */ new Map();
|
|
1112
1782
|
const planCache = /* @__PURE__ */ new WeakMap();
|
|
1113
1783
|
const opCaches = /* @__PURE__ */ new Map();
|
|
@@ -1254,6 +1924,17 @@ function createPlanner(config) {
|
|
|
1254
1924
|
return value;
|
|
1255
1925
|
};
|
|
1256
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
|
+
};
|
|
1257
1938
|
const buildInfixLeft = (arg) => {
|
|
1258
1939
|
const operand = plan(arg.operand);
|
|
1259
1940
|
const operator = arg.operator === void 0 ? null : plan(arg.operator);
|
|
@@ -1379,6 +2060,26 @@ function createPlanner(config) {
|
|
|
1379
2060
|
return value;
|
|
1380
2061
|
};
|
|
1381
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);
|
|
1382
2083
|
const buildPrefix = (arg) => {
|
|
1383
2084
|
const operator = plan(arg.operator);
|
|
1384
2085
|
const operand = plan(arg.operand);
|
|
@@ -1462,6 +2163,31 @@ function createPlanner(config) {
|
|
|
1462
2163
|
return value;
|
|
1463
2164
|
};
|
|
1464
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
|
+
}
|
|
1465
2191
|
return (state) => {
|
|
1466
2192
|
tick(state);
|
|
1467
2193
|
if (!state.track) return slot.fn(state);
|
|
@@ -1540,6 +2266,9 @@ function createPlanner(config) {
|
|
|
1540
2266
|
infixRight: buildInfixRight,
|
|
1541
2267
|
as: buildAs,
|
|
1542
2268
|
between: buildBetween,
|
|
2269
|
+
flag: buildFlag,
|
|
2270
|
+
pick: buildPick,
|
|
2271
|
+
yields: buildYields,
|
|
1543
2272
|
prefix: buildPrefix,
|
|
1544
2273
|
postfix: buildPostfix,
|
|
1545
2274
|
manySep: (arg) => planSeparated(arg, false),
|
|
@@ -1559,7 +2288,7 @@ function createPlanner(config) {
|
|
|
1559
2288
|
}
|
|
1560
2289
|
function planSeq(children) {
|
|
1561
2290
|
if (!Array.isArray(children)) throw new Error("'seq' expects an array of parsers");
|
|
1562
|
-
const idiom = recognizeSeqIdiom(children);
|
|
2291
|
+
const idiom = idioms ? recognizeSeqIdiom(children) : null;
|
|
1563
2292
|
if (idiom !== null) return planUncached(idiom);
|
|
1564
2293
|
const PARSER = 0;
|
|
1565
2294
|
const LABELLED = 1;
|
|
@@ -1673,7 +2402,9 @@ function createPlanner(config) {
|
|
|
1673
2402
|
}
|
|
1674
2403
|
function planAlt(branches) {
|
|
1675
2404
|
if (!Array.isArray(branches)) throw new Error("'alt' expects an array of branches");
|
|
1676
|
-
const
|
|
2405
|
+
const oneOf = idioms ? recognizeAltConsumeIdiom(branches) : null;
|
|
2406
|
+
if (oneOf !== null) return planUncached(oneOf);
|
|
2407
|
+
const factored = idioms ? recognizeAltPrefixIdiom(branches) : null;
|
|
1677
2408
|
if (factored !== null) return planUncached(factored);
|
|
1678
2409
|
const planned = branches.map(plan);
|
|
1679
2410
|
const table = firsts ? buildDispatch(branches, firsts) : null;
|
|
@@ -1795,6 +2526,9 @@ var OPERATORS = [
|
|
|
1795
2526
|
"prefix",
|
|
1796
2527
|
"postfix",
|
|
1797
2528
|
"between",
|
|
2529
|
+
"pick",
|
|
2530
|
+
"yields",
|
|
2531
|
+
"flag",
|
|
1798
2532
|
"as",
|
|
1799
2533
|
"subrule",
|
|
1800
2534
|
"label",
|
|
@@ -1849,7 +2583,7 @@ var createValueMethods = () => ({
|
|
|
1849
2583
|
|
|
1850
2584
|
// src/codegen.js
|
|
1851
2585
|
function createCodegen(config) {
|
|
1852
|
-
const { planner, rules, firsts, memo, maxSteps, tokenIds = null } = config;
|
|
2586
|
+
const { planner, rules, firsts, memo, maxSteps, tokenIds = null, memoSlots = /* @__PURE__ */ new Map(), inlineRules = true, idioms = true } = config;
|
|
1853
2587
|
const typeCheck = (tokenVar, name) => {
|
|
1854
2588
|
const id = tokenIds ? tokenIds.get(name) : void 0;
|
|
1855
2589
|
if (id === void 0) return `${tokenVar}.type === ${JSON.stringify(name)}`;
|
|
@@ -1870,6 +2604,33 @@ function createCodegen(config) {
|
|
|
1870
2604
|
}
|
|
1871
2605
|
const actionHelper = (logic) => helper(planner.planAction(logic), { kind: "action", logic });
|
|
1872
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]));
|
|
1873
2634
|
function indexOfRule(name) {
|
|
1874
2635
|
let index = ruleIndex.get(name);
|
|
1875
2636
|
if (index !== void 0) return index;
|
|
@@ -1930,6 +2691,16 @@ function createCodegen(config) {
|
|
|
1930
2691
|
);
|
|
1931
2692
|
return result;
|
|
1932
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
|
+
}
|
|
1933
2704
|
case "many":
|
|
1934
2705
|
case "many1": {
|
|
1935
2706
|
const atLeastOne = op === "many1";
|
|
@@ -1947,7 +2718,7 @@ function createCodegen(config) {
|
|
|
1947
2718
|
);
|
|
1948
2719
|
}
|
|
1949
2720
|
out.push("for (;;) {", `const ${before} = c.idx;`);
|
|
1950
|
-
const value = gen(arg, out, bindingsVar);
|
|
2721
|
+
const value = atLeastOne ? cold(() => gen(arg, out, bindingsVar)) : gen(arg, out, bindingsVar);
|
|
1951
2722
|
out.push(
|
|
1952
2723
|
`if (${value} === F) { c.idx = ${before}; break; }`,
|
|
1953
2724
|
`if (c.idx === ${before}) P(${JSON.stringify(op)}, ${before});`,
|
|
@@ -1977,7 +2748,7 @@ function createCodegen(config) {
|
|
|
1977
2748
|
operatorVar = gen(arg.operator, out, bindingsVar);
|
|
1978
2749
|
out.push(`if (${operatorVar} === F) { c.idx = ${mark}; break; }`);
|
|
1979
2750
|
}
|
|
1980
|
-
const right = gen(arg.operand, out, bindingsVar);
|
|
2751
|
+
const right = cold(() => gen(arg.operand, out, bindingsVar));
|
|
1981
2752
|
out.push(
|
|
1982
2753
|
`if (${right} === F) { c.idx = ${mark}; break; }`,
|
|
1983
2754
|
`if (${bindings} === null) ${bindings} = { left: null, op: null, right: null };`,
|
|
@@ -1992,12 +2763,27 @@ function createCodegen(config) {
|
|
|
1992
2763
|
case "oneOf": {
|
|
1993
2764
|
const names = Array.isArray(arg) ? arg : [arg];
|
|
1994
2765
|
const token = nextId();
|
|
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
|
+
}
|
|
1995
2781
|
const test = names.map((n) => typeCheck(token, n)).join(" || ");
|
|
1996
2782
|
out.push(
|
|
1997
2783
|
`let ${result};`,
|
|
1998
2784
|
`const ${token} = c.tokens[c.idx];`,
|
|
1999
2785
|
`if (${token} !== undefined && (${test})) { c.idx++; ${result} = ${token}; }`,
|
|
2000
|
-
|
|
2786
|
+
failed
|
|
2001
2787
|
);
|
|
2002
2788
|
return result;
|
|
2003
2789
|
}
|
|
@@ -2030,7 +2816,7 @@ function createCodegen(config) {
|
|
|
2030
2816
|
operatorVar = gen(arg.operator, out, bindingsVar);
|
|
2031
2817
|
out.push(`if (${operatorVar} === F) { c.idx = ${mark}; break; }`);
|
|
2032
2818
|
}
|
|
2033
|
-
const right = gen(arg.operand, out, bindingsVar);
|
|
2819
|
+
const right = cold(() => gen(arg.operand, out, bindingsVar));
|
|
2034
2820
|
out.push(
|
|
2035
2821
|
`if (${right} === F) { c.idx = ${mark}; break; }`,
|
|
2036
2822
|
`if (${items} === null) ${items} = [];`,
|
|
@@ -2105,6 +2891,19 @@ function createCodegen(config) {
|
|
|
2105
2891
|
);
|
|
2106
2892
|
return result;
|
|
2107
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
|
+
}
|
|
2108
2907
|
case "prefix": {
|
|
2109
2908
|
const label2 = "L" + nextId();
|
|
2110
2909
|
const start = nextId();
|
|
@@ -2181,7 +2980,7 @@ function createCodegen(config) {
|
|
|
2181
2980
|
);
|
|
2182
2981
|
const sep = gen(sepNode, out, bindingsVar);
|
|
2183
2982
|
out.push(`if (${sep} === F) { c.idx = ${beforeSep}; break; }`, `const ${beforeItem} = c.idx;`);
|
|
2184
|
-
const item = gen(arg.rule, out, bindingsVar);
|
|
2983
|
+
const item = cold(() => gen(arg.rule, out, bindingsVar));
|
|
2185
2984
|
out.push(
|
|
2186
2985
|
`if (${item} === F) { c.idx = ${trailing ? beforeItem : beforeSep}; break; }`,
|
|
2187
2986
|
`${result}.push(${item});`,
|
|
@@ -2196,8 +2995,20 @@ function createCodegen(config) {
|
|
|
2196
2995
|
out.push(`const ${result} = ${helper(planner.plan(node), { kind: "unknownRule", name })}(c);`);
|
|
2197
2996
|
return result;
|
|
2198
2997
|
}
|
|
2199
|
-
const index = indexOfRule(name);
|
|
2200
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);
|
|
2201
3012
|
out.push(tickSrc);
|
|
2202
3013
|
if (memo) {
|
|
2203
3014
|
const key = nextId();
|
|
@@ -2218,6 +3029,26 @@ function createCodegen(config) {
|
|
|
2218
3029
|
`c.memo.set(${key}, { value: ${result}, end: c.idx });`,
|
|
2219
3030
|
"}"
|
|
2220
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
|
+
);
|
|
2221
3052
|
} else {
|
|
2222
3053
|
out.push(
|
|
2223
3054
|
`const ${parent} = c.ruleDepth;`,
|
|
@@ -2235,7 +3066,7 @@ function createCodegen(config) {
|
|
|
2235
3066
|
}
|
|
2236
3067
|
function genSeq(children, out, result) {
|
|
2237
3068
|
if (!Array.isArray(children)) throw new Error("'seq' expects an array of parsers");
|
|
2238
|
-
const idiom = recognizeSeqIdiom(children);
|
|
3069
|
+
const idiom = idioms ? recognizeSeqIdiom(children) : null;
|
|
2239
3070
|
if (idiom !== null) return gen(idiom, out, null);
|
|
2240
3071
|
let hasLabels = false;
|
|
2241
3072
|
let hasAction = false;
|
|
@@ -2286,7 +3117,9 @@ function createCodegen(config) {
|
|
|
2286
3117
|
}
|
|
2287
3118
|
function genAlt(branches, out, result, bindingsVar) {
|
|
2288
3119
|
if (!Array.isArray(branches)) throw new Error("'alt' expects an array of branches");
|
|
2289
|
-
const
|
|
3120
|
+
const oneOf = idioms ? recognizeAltConsumeIdiom(branches) : null;
|
|
3121
|
+
if (oneOf !== null) return gen(oneOf, out, bindingsVar);
|
|
3122
|
+
const factored = idioms ? recognizeAltPrefixIdiom(branches) : null;
|
|
2290
3123
|
if (factored !== null) return gen(factored, out, bindingsVar);
|
|
2291
3124
|
const table = firsts ? buildDispatch(branches, firsts) : null;
|
|
2292
3125
|
const label2 = "L" + nextId();
|
|
@@ -2380,6 +3213,165 @@ return ${value};
|
|
|
2380
3213
|
return { compile, functionFor, sourceFor, ruleIndex, RULES, SOURCES, ORIGINS, guard };
|
|
2381
3214
|
}
|
|
2382
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
|
+
|
|
2383
3375
|
// src/parser.js
|
|
2384
3376
|
var EXECUTION_MODES = /* @__PURE__ */ new Set(["generated", "interpreted"]);
|
|
2385
3377
|
var GrammarError = class extends Error {
|
|
@@ -2395,6 +3387,9 @@ function createParser(spec, options = {}) {
|
|
|
2395
3387
|
const { tokens: tokenDefs, rules, start } = spec;
|
|
2396
3388
|
const {
|
|
2397
3389
|
memo = false,
|
|
3390
|
+
autoMemo = true,
|
|
3391
|
+
inlineRules = true,
|
|
3392
|
+
idioms = true,
|
|
2398
3393
|
strict = true,
|
|
2399
3394
|
validate = true,
|
|
2400
3395
|
maxSteps = 0,
|
|
@@ -2422,9 +3417,11 @@ function createParser(spec, options = {}) {
|
|
|
2422
3417
|
const startRule = rules[start];
|
|
2423
3418
|
const firsts = ll1 ? analysis.firsts : null;
|
|
2424
3419
|
const tokenIds = new Map(lexer.tokenNames.map((name, id) => [name, id]));
|
|
2425
|
-
const
|
|
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 });
|
|
2426
3423
|
engine.__jlGrammar = { planner };
|
|
2427
|
-
const codegen = compile && unsafeEval ? createCodegen({ planner, rules, firsts, memo, maxSteps, tokenIds }) : null;
|
|
3424
|
+
const codegen = compile && unsafeEval ? createCodegen({ planner, rules, firsts, memo, maxSteps, tokenIds, memoSlots, inlineRules, idioms }) : null;
|
|
2428
3425
|
const compiledStart = codegen && codegen.compile(start);
|
|
2429
3426
|
if (codegen && !engine.disableInterpretedOptimization) {
|
|
2430
3427
|
for (const name of Object.keys(rules)) {
|
|
@@ -2433,7 +3430,7 @@ function createParser(spec, options = {}) {
|
|
|
2433
3430
|
}
|
|
2434
3431
|
}
|
|
2435
3432
|
function parseTokens(tokens, source, track = false) {
|
|
2436
|
-
const state = createState(tokens, rules, { memo, maxSteps, track });
|
|
3433
|
+
const state = createState(tokens, rules, { memo, maxSteps, track, memoSlots: memoRules.length });
|
|
2437
3434
|
state.firsts = firsts;
|
|
2438
3435
|
state.source = source;
|
|
2439
3436
|
state.positions = lexer.positions;
|
|
@@ -2463,6 +3460,8 @@ function createParser(spec, options = {}) {
|
|
|
2463
3460
|
planner,
|
|
2464
3461
|
codegen,
|
|
2465
3462
|
execution: mode,
|
|
3463
|
+
/** Rules memoized by `autoMemo`, for introspection. */
|
|
3464
|
+
memoRules,
|
|
2466
3465
|
compiled: Boolean(compiledStart),
|
|
2467
3466
|
/** Rules whose `alt`s resolved to O(1) LL(1) dispatch, for introspection. */
|
|
2468
3467
|
get firsts() {
|
|
@@ -2768,6 +3767,7 @@ var CONSTRUCTORS = {
|
|
|
2768
3767
|
many: { arity: [1, 1], of: ([a]) => ({ many: a }) },
|
|
2769
3768
|
many1: { arity: [1, 1], of: ([a]) => ({ many1: a }) },
|
|
2770
3769
|
option: { arity: [1, 1], of: ([a]) => ({ option: a }) },
|
|
3770
|
+
flag: { arity: [1, 1], of: ([a]) => ({ flag: a }) },
|
|
2771
3771
|
action: { arity: [1, 1], of: ([a]) => ({ action: a }) },
|
|
2772
3772
|
lookahead: { arity: [1, 1], of: ([a]) => ({ lookahead: a }) },
|
|
2773
3773
|
negLookahead: { arity: [1, 1], of: ([a]) => ({ negLookahead: a }) },
|
|
@@ -2779,6 +3779,10 @@ var CONSTRUCTORS = {
|
|
|
2779
3779
|
manySep: { arity: [2, 2], of: ([a, b]) => ({ manySep: { rule: a, sep: b } }) },
|
|
2780
3780
|
many1Sep: { arity: [2, 2], of: ([a, b]) => ({ many1Sep: { rule: a, sep: b } }) },
|
|
2781
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] } }) },
|
|
2782
3786
|
infixLeft: { arity: [3, 3], of: ([a, b, c]) => ({ infixLeft: { operand: a, operator: b, combine: logicOf(c) } }) },
|
|
2783
3787
|
infixRight: { arity: [3, 3], of: ([a, b, c]) => ({ infixRight: { operand: a, operator: b, combine: logicOf(c) } }) },
|
|
2784
3788
|
prefix: { arity: [3, 3], of: ([a, b, c]) => ({ prefix: { operator: a, operand: b, combine: logicOf(c) } }) },
|
|
@@ -2799,6 +3803,355 @@ function createParserFromSource(source, options = {}) {
|
|
|
2799
3803
|
return createParser(grammar, { ...options, methods });
|
|
2800
3804
|
}
|
|
2801
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
|
+
|
|
2802
4155
|
// src/emit.js
|
|
2803
4156
|
import { Compiler, LogicEngine as LogicEngine2 } from "json-logic-engine";
|
|
2804
4157
|
var UNSUPPORTED = /\b(coerceArray|precoerceNumber|compareCheck|assertAllowedDepth|assertSize|asyncIterators|notTraversed)\b|(?<!engine\.)\bmethods\[|engine\.(?!methods\b)/;
|
|
@@ -2847,11 +4200,21 @@ function emitModule(spec, options = {}) {
|
|
|
2847
4200
|
const { tokens: tokenDefs, rules, start } = spec;
|
|
2848
4201
|
const {
|
|
2849
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,
|
|
2850
4210
|
strict = true,
|
|
2851
4211
|
ll1 = true,
|
|
2852
4212
|
maxSteps = 0,
|
|
2853
4213
|
positions = "full",
|
|
2854
4214
|
execution = "generated",
|
|
4215
|
+
lexer: lexerMode = "generated",
|
|
4216
|
+
scanners = true,
|
|
4217
|
+
fuse = true,
|
|
2855
4218
|
format = "esm",
|
|
2856
4219
|
validate = true,
|
|
2857
4220
|
moduleName = spec.name || "parser",
|
|
@@ -2860,6 +4223,7 @@ function emitModule(spec, options = {}) {
|
|
|
2860
4223
|
methods: semanticMethods
|
|
2861
4224
|
} = options;
|
|
2862
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)}`);
|
|
2863
4227
|
const methodsBlock = options.methodsBlock !== void 0 ? options.methodsBlock : spec.methodsBlock;
|
|
2864
4228
|
const block = methodsBlock ? emitMethodsBlock(methodsBlock, format) : null;
|
|
2865
4229
|
if (!start || !(start in rules)) throw new Error(`emitModule: start rule '${start}' is not defined in rules`);
|
|
@@ -2872,8 +4236,10 @@ function emitModule(spec, options = {}) {
|
|
|
2872
4236
|
throw new Error("not compiled during emit");
|
|
2873
4237
|
}, run: () => {
|
|
2874
4238
|
} };
|
|
2875
|
-
const
|
|
2876
|
-
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 });
|
|
2877
4243
|
codegen.compile(start);
|
|
2878
4244
|
const reached = [...codegen.ruleIndex.entries()].sort((a, b) => a[1] - b[1]);
|
|
2879
4245
|
const unsupported = codegen.ORIGINS.map((origin, index) => ({ origin, index })).filter(({ origin }) => origin.kind === "unknownRule");
|
|
@@ -2914,7 +4280,8 @@ R[${index}] = ${codegen.SOURCES[index]}`
|
|
|
2914
4280
|
).join("\n\n");
|
|
2915
4281
|
const interpretedActions = execution === "interpreted";
|
|
2916
4282
|
const cjs = format === "cjs";
|
|
2917
|
-
const
|
|
4283
|
+
const generatedLexer = lexerMode === "generated";
|
|
4284
|
+
const runtimeImports = ["FAIL as F", "expect as E", "createState", "buildParseError", generatedLexer ? "LexError" : "createLexer"];
|
|
2918
4285
|
if (needsEngine) runtimeImports.push("createValueMethods");
|
|
2919
4286
|
const importRuntime = cjs ? `const { ${runtimeImports.map((b) => b.replace(" as ", ": ")).join(", ")} } = require(${quote(runtimeSpecifier)})` : `import { ${runtimeImports.join(", ")} } from ${quote(runtimeSpecifier)}`;
|
|
2920
4287
|
const importEngine = engineFree ? "" : cjs ? `const { LogicEngine } = require(${quote(engineSpecifier)})
|
|
@@ -2933,7 +4300,7 @@ export default { ${PUBLIC.join(", ")} }`;
|
|
|
2933
4300
|
//
|
|
2934
4301
|
// The grammar was analyzed when this file was written: FIRST sets, left-recursion
|
|
2935
4302
|
// checks and rule specialization all happened then. Importing it does no grammar
|
|
2936
|
-
// 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." : ""}
|
|
2937
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."}
|
|
2938
4305
|
|
|
2939
4306
|
${importRuntime}
|
|
@@ -2943,7 +4310,7 @@ const tokens = ${literal(tokenDefs)}
|
|
|
2943
4310
|
|
|
2944
4311
|
const start = ${quote(start)}
|
|
2945
4312
|
|
|
2946
|
-
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} })`}
|
|
2947
4314
|
const tokenize = lexer.tokenize
|
|
2948
4315
|
|
|
2949
4316
|
${engineFree ? `// Every semantic action compiled to source, so the engine reduced to a table
|
|
@@ -3034,7 +4401,7 @@ const RULES = ${literal(Object.fromEntries(reached.map(([name, index]) => [name,
|
|
|
3034
4401
|
* @param {string} [source]
|
|
3035
4402
|
*/
|
|
3036
4403
|
function parseTokens (tokenList, source, track = false) {
|
|
3037
|
-
const state = createState(tokenList, {}, { memo: ${memo}, maxSteps: ${maxSteps}, track })
|
|
4404
|
+
const state = createState(tokenList, {}, { memo: ${memo}, maxSteps: ${maxSteps}, track, memoSlots: ${memoRules.length} })
|
|
3038
4405
|
state.source = source
|
|
3039
4406
|
state.positions = ${quote(positions)}
|
|
3040
4407
|
state.ruleNames[0] = start
|