parseman 0.38.0 → 0.39.1
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/dist/analysis/duplication.d.ts.map +1 -1
- package/dist/analysis/gating.d.ts.map +1 -1
- package/dist/combinators/attempt.d.ts.map +1 -1
- package/dist/combinators/choice.d.ts.map +1 -1
- package/dist/combinators/dispatch.d.ts +53 -0
- package/dist/combinators/dispatch.d.ts.map +1 -0
- package/dist/combinators/expect.d.ts.map +1 -1
- package/dist/combinators/first-set.d.ts.map +1 -1
- package/dist/combinators/repeat.d.ts.map +1 -1
- package/dist/compiler/codegen.d.ts.map +1 -1
- package/dist/compiler/fields.d.ts.map +1 -1
- package/dist/compiler/grammar-coverage-ids.d.ts +2 -1
- package/dist/compiler/grammar-coverage-ids.d.ts.map +1 -1
- package/dist/compiler/ir-serialize.d.ts.map +1 -1
- package/dist/compiler/value-usage.d.ts.map +1 -1
- package/dist/coverage.d.ts.map +1 -1
- package/dist/index.cjs +772 -33
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +772 -33
- package/dist/index.js.map +4 -4
- package/dist/language-service/index.cjs.map +2 -2
- package/dist/language-service/index.js.map +2 -2
- package/dist/plugin/evaluator.d.ts +6 -0
- package/dist/plugin/evaluator.d.ts.map +1 -1
- package/dist/plugin/index.cjs +840 -33
- package/dist/plugin/index.cjs.map +4 -4
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +840 -33
- package/dist/plugin/index.js.map +4 -4
- package/dist/spec/index.cjs +294 -1
- package/dist/spec/index.cjs.map +4 -4
- package/dist/spec/index.js +294 -1
- package/dist/spec/index.js.map +4 -4
- package/dist/spec/model.d.ts.map +1 -1
- package/dist/types.d.ts +37 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
package/dist/spec/index.cjs
CHANGED
|
@@ -82,6 +82,8 @@ function matchesEmpty(p, seen = /* @__PURE__ */ new Set(), resolve) {
|
|
|
82
82
|
return d.value.length === 0;
|
|
83
83
|
case "keywords":
|
|
84
84
|
return false;
|
|
85
|
+
case "routed":
|
|
86
|
+
return false;
|
|
85
87
|
case "regex":
|
|
86
88
|
try {
|
|
87
89
|
const m = new RegExp(d.source).exec("");
|
|
@@ -108,6 +110,8 @@ function matchesEmpty(p, seen = /* @__PURE__ */ new Set(), resolve) {
|
|
|
108
110
|
return d.parsers.every(me);
|
|
109
111
|
case "choice":
|
|
110
112
|
return d.parsers.some(me);
|
|
113
|
+
case "dispatch":
|
|
114
|
+
return me(d.selector);
|
|
111
115
|
case "transform":
|
|
112
116
|
case "label":
|
|
113
117
|
case "trivia":
|
|
@@ -181,6 +185,8 @@ function firstSetOf(p, seen = /* @__PURE__ */ new Set(), resolve) {
|
|
|
181
185
|
case "keywords":
|
|
182
186
|
return p._meta.firstSet;
|
|
183
187
|
// terminals: no refs, cached set is exact
|
|
188
|
+
case "dispatch":
|
|
189
|
+
return fs(d.selector);
|
|
184
190
|
case "lazy":
|
|
185
191
|
try {
|
|
186
192
|
return fs(d.thunk());
|
|
@@ -313,6 +319,12 @@ function markUnusedValues(root) {
|
|
|
313
319
|
case "choice":
|
|
314
320
|
for (const p of def.parsers) visit(p, consumed);
|
|
315
321
|
return;
|
|
322
|
+
case "dispatch":
|
|
323
|
+
visit(def.selector, consumed);
|
|
324
|
+
for (const entry of def.cases) visit(entry.parser, consumed);
|
|
325
|
+
if (def.matchers) for (const entry of def.matchers) visit(entry.parser, consumed);
|
|
326
|
+
if (def.otherwise) visit(def.otherwise, consumed);
|
|
327
|
+
return;
|
|
316
328
|
case "node":
|
|
317
329
|
visit(def.parser, false);
|
|
318
330
|
return;
|
|
@@ -723,6 +735,8 @@ function derive(c, seen) {
|
|
|
723
735
|
return [def.label];
|
|
724
736
|
case "choice":
|
|
725
737
|
return def.parsers.flatMap(deriveExpected2);
|
|
738
|
+
case "dispatch":
|
|
739
|
+
return deriveExpected2(def.selector);
|
|
726
740
|
// Union through the NULLABLE prefix, mirroring `sequenceFirstSet`. A leading
|
|
727
741
|
// `optional(…)`/`many(…)` can match nothing, so the term after it is equally
|
|
728
742
|
// able to fail first — deriving from term 0 alone named a token the parse
|
|
@@ -762,6 +776,8 @@ function derive(c, seen) {
|
|
|
762
776
|
case "not":
|
|
763
777
|
case "peek":
|
|
764
778
|
return deriveExpected2(def.parser);
|
|
779
|
+
case "routed":
|
|
780
|
+
return ["routed()"];
|
|
765
781
|
case "lazy": {
|
|
766
782
|
const name = c._ruleName;
|
|
767
783
|
if (seen.has(c)) return name ? [name] : [];
|
|
@@ -861,6 +877,7 @@ function choice(...args) {
|
|
|
861
877
|
const result = parsers[idx].parse(input, pos, ctx);
|
|
862
878
|
if (result.ok) return result;
|
|
863
879
|
expected.push(...result.expected);
|
|
880
|
+
if (result.committed) return { ok: false, expected, span: { start: pos, end: pos }, committed: true };
|
|
864
881
|
return { ok: false, expected, span: { start: pos, end: pos } };
|
|
865
882
|
}
|
|
866
883
|
return {
|
|
@@ -901,6 +918,7 @@ function choice(...args) {
|
|
|
901
918
|
rollbackCstCapture(ctx, mark);
|
|
902
919
|
if (logLen !== void 0 && ctx._triviaLog && ctx._triviaLog.length !== logLen) ctx._triviaLog.length = logLen;
|
|
903
920
|
expected.push(...result.expected);
|
|
921
|
+
if (result.committed) return { ok: false, expected, span: result.span, committed: true };
|
|
904
922
|
continue;
|
|
905
923
|
}
|
|
906
924
|
const checks = autoNot[i];
|
|
@@ -1851,6 +1869,7 @@ function repItem(combinator, input, cur, ctx, guardable) {
|
|
|
1851
1869
|
const result = combinator.parse(input, pos, ctx);
|
|
1852
1870
|
if (!result.ok) {
|
|
1853
1871
|
rollbackTrivia(ctx, mark);
|
|
1872
|
+
if (result.committed) return { fail: result, failPos: pos };
|
|
1854
1873
|
return { fail: result, failPos: pos };
|
|
1855
1874
|
}
|
|
1856
1875
|
if (result.span.end === pos) {
|
|
@@ -1894,6 +1913,7 @@ function many(combinator, opts = {}) {
|
|
|
1894
1913
|
const item = repItem(combinator, input, cur, ctx, guardable);
|
|
1895
1914
|
if (item === "stop") break;
|
|
1896
1915
|
if ("fail" in item) {
|
|
1916
|
+
if (!item.fail.ok && item.fail.committed) return item.fail;
|
|
1897
1917
|
const sync = ctx._tolerant ? ctx._sync : void 0;
|
|
1898
1918
|
if (sync === void 0) break;
|
|
1899
1919
|
if (matchesAt(sync, input, item.failPos, ctx)) break;
|
|
@@ -1938,6 +1958,7 @@ function atLeast(combinator, min, max) {
|
|
|
1938
1958
|
while (count < min) {
|
|
1939
1959
|
const item = repItem(combinator, input, cur, ctx, guardable);
|
|
1940
1960
|
if (item === "stop" || "fail" in item) {
|
|
1961
|
+
if (item !== "stop" && !item.fail.ok && item.fail.committed) return item.fail;
|
|
1941
1962
|
expected ??= deriveExpected(combinator);
|
|
1942
1963
|
return { ok: false, expected: expected.length > 0 ? expected : [combinator._tag], span: { start: cur, end: cur } };
|
|
1943
1964
|
}
|
|
@@ -1950,6 +1971,7 @@ function atLeast(combinator, min, max) {
|
|
|
1950
1971
|
const item = repItem(combinator, input, cur, ctx, guardable);
|
|
1951
1972
|
if (item === "stop") break;
|
|
1952
1973
|
if ("fail" in item) {
|
|
1974
|
+
if (!item.fail.ok && item.fail.committed) return item.fail;
|
|
1953
1975
|
const sync = ctx._tolerant ? ctx._sync : void 0;
|
|
1954
1976
|
if (sync === void 0) break;
|
|
1955
1977
|
if (matchesAt(sync, input, item.failPos, ctx)) break;
|
|
@@ -1988,6 +2010,7 @@ function optional(combinator) {
|
|
|
1988
2010
|
const result = combinator.parse(input, pos, ctx);
|
|
1989
2011
|
if (result.ok) return result;
|
|
1990
2012
|
rollbackTrivia(ctx, mark);
|
|
2013
|
+
if (result.committed) return result;
|
|
1991
2014
|
return { ok: true, value: null, span: { start: pos, end: pos } };
|
|
1992
2015
|
}
|
|
1993
2016
|
};
|
|
@@ -2024,6 +2047,7 @@ function sepBy(combinator, separator, opts = {}) {
|
|
|
2024
2047
|
values.push(first.value);
|
|
2025
2048
|
cur = first.span.end;
|
|
2026
2049
|
} else {
|
|
2050
|
+
if (first.committed) return first;
|
|
2027
2051
|
const term = ctx._tolerant ? ctx._sync : void 0;
|
|
2028
2052
|
if (term === void 0 || matchesAt(term, input, pos, ctx)) {
|
|
2029
2053
|
if (min >= 1) return failAt2(pos);
|
|
@@ -2051,6 +2075,7 @@ function sepBy(combinator, separator, opts = {}) {
|
|
|
2051
2075
|
const sep = separator.parse(input, sepPos, ctx);
|
|
2052
2076
|
if (!sep.ok) {
|
|
2053
2077
|
rollbackTrivia(ctx, loopMark);
|
|
2078
|
+
if (sep.committed) return sep;
|
|
2054
2079
|
break;
|
|
2055
2080
|
}
|
|
2056
2081
|
const sepMark = trailing === "forbid" ? void 0 : saveTriviaMark(ctx);
|
|
@@ -2066,6 +2091,7 @@ function sepBy(combinator, separator, opts = {}) {
|
|
|
2066
2091
|
}
|
|
2067
2092
|
const next = combinator.parse(input, nextPos, ctx);
|
|
2068
2093
|
if (!next.ok) {
|
|
2094
|
+
if (next.committed) return next;
|
|
2069
2095
|
const term = ctx._tolerant ? ctx._sync : void 0;
|
|
2070
2096
|
if (term !== void 0 && !matchesAt(term, input, nextPos, ctx)) {
|
|
2071
2097
|
expected ??= deriveExpected(combinator);
|
|
@@ -2299,6 +2325,8 @@ function parserHasOwnFields(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
2299
2325
|
case "sequence":
|
|
2300
2326
|
case "choice":
|
|
2301
2327
|
return d.parsers.some((x) => parserHasOwnFields(x, seen));
|
|
2328
|
+
case "dispatch":
|
|
2329
|
+
return parserHasOwnFields(d.selector, seen) || d.cases.some((x) => parserHasOwnFields(x.parser, seen)) || (d.matchers ? d.matchers.some((entry) => parserHasOwnFields(entry.parser, seen)) : false) || (d.otherwise ? parserHasOwnFields(d.otherwise, seen) : false);
|
|
2302
2330
|
case "sepBy":
|
|
2303
2331
|
return parserHasOwnFields(d.parser, seen) || parserHasOwnFields(d.separator, seen);
|
|
2304
2332
|
case "skip":
|
|
@@ -2413,6 +2441,245 @@ function keywords(words, opts = {}) {
|
|
|
2413
2441
|
};
|
|
2414
2442
|
}
|
|
2415
2443
|
|
|
2444
|
+
// src/combinators/dispatch.ts
|
|
2445
|
+
function parserUsesRouted(parser2, seen = /* @__PURE__ */ new Set()) {
|
|
2446
|
+
if (seen.has(parser2)) return false;
|
|
2447
|
+
seen.add(parser2);
|
|
2448
|
+
const def = parser2._def;
|
|
2449
|
+
switch (def.tag) {
|
|
2450
|
+
case "routed":
|
|
2451
|
+
return true;
|
|
2452
|
+
case "sequence":
|
|
2453
|
+
case "choice":
|
|
2454
|
+
return def.parsers.some((entry) => parserUsesRouted(entry, seen));
|
|
2455
|
+
case "dispatch":
|
|
2456
|
+
return false;
|
|
2457
|
+
case "sepBy":
|
|
2458
|
+
return parserUsesRouted(def.parser, seen) || parserUsesRouted(def.separator, seen);
|
|
2459
|
+
case "skip":
|
|
2460
|
+
return parserUsesRouted(def.main, seen) || parserUsesRouted(def.skipped, seen);
|
|
2461
|
+
case "grammar":
|
|
2462
|
+
return parserUsesRouted(def.parser, seen) || (def.triviaParser ? parserUsesRouted(def.triviaParser, seen) : false);
|
|
2463
|
+
case "scanTo":
|
|
2464
|
+
return parserUsesRouted(def.sentinel, seen) || def.skip.some((entry) => parserUsesRouted(entry, seen));
|
|
2465
|
+
case "recover":
|
|
2466
|
+
return parserUsesRouted(def.parser, seen) || parserUsesRouted(def.sentinel, seen);
|
|
2467
|
+
case "lazy":
|
|
2468
|
+
try {
|
|
2469
|
+
return parserUsesRouted(def.thunk(), seen);
|
|
2470
|
+
} catch {
|
|
2471
|
+
return false;
|
|
2472
|
+
}
|
|
2473
|
+
case "many":
|
|
2474
|
+
case "oneOrMore":
|
|
2475
|
+
case "optional":
|
|
2476
|
+
case "attempt":
|
|
2477
|
+
case "transform":
|
|
2478
|
+
case "trivia":
|
|
2479
|
+
case "token":
|
|
2480
|
+
case "leaf":
|
|
2481
|
+
case "label":
|
|
2482
|
+
case "field":
|
|
2483
|
+
case "not":
|
|
2484
|
+
case "peek":
|
|
2485
|
+
case "node":
|
|
2486
|
+
case "withCtx":
|
|
2487
|
+
case "expect":
|
|
2488
|
+
return parserUsesRouted(def.parser, seen);
|
|
2489
|
+
default:
|
|
2490
|
+
return false;
|
|
2491
|
+
}
|
|
2492
|
+
}
|
|
2493
|
+
function branchUsesRouted(branch) {
|
|
2494
|
+
return branch.usesRouted === true || parserUsesRouted(branch.parser);
|
|
2495
|
+
}
|
|
2496
|
+
function asciiFoldKey(key) {
|
|
2497
|
+
let out = "";
|
|
2498
|
+
for (let i = 0; i < key.length; i++) {
|
|
2499
|
+
const c = key.charCodeAt(i);
|
|
2500
|
+
out += String.fromCharCode(c >= 65 && c <= 90 ? c + 32 : c);
|
|
2501
|
+
}
|
|
2502
|
+
return out;
|
|
2503
|
+
}
|
|
2504
|
+
function matchesDispatchMatcher(value, matcher) {
|
|
2505
|
+
const candidate = matcher.caseInsensitive ? asciiFoldKey(value) : value;
|
|
2506
|
+
const expected = matcher.caseInsensitive ? asciiFoldKey(matcher.value) : matcher.value;
|
|
2507
|
+
switch (matcher.kind) {
|
|
2508
|
+
case "startsWith":
|
|
2509
|
+
return candidate.startsWith(expected);
|
|
2510
|
+
case "endsWith":
|
|
2511
|
+
return candidate.endsWith(expected);
|
|
2512
|
+
case "matches": {
|
|
2513
|
+
const flags = matcher.caseInsensitive && !matcher.flags?.includes("i") ? `${matcher.flags ?? ""}i` : matcher.flags ?? "";
|
|
2514
|
+
return new RegExp(matcher.value, flags).test(value);
|
|
2515
|
+
}
|
|
2516
|
+
}
|
|
2517
|
+
}
|
|
2518
|
+
function isStringMatcher(value) {
|
|
2519
|
+
if (typeof value !== "object" || value === null) return false;
|
|
2520
|
+
const kind = value.kind;
|
|
2521
|
+
return kind === "startsWith" || kind === "endsWith" || kind === "matches";
|
|
2522
|
+
}
|
|
2523
|
+
function fixedMatcher(kind, value) {
|
|
2524
|
+
if (typeof value !== "string") throw new TypeError(`parseman: ${kind}() value must be a string`);
|
|
2525
|
+
if (value.length === 0) throw new RangeError(`parseman: ${kind}() requires a non-empty value`);
|
|
2526
|
+
return { kind, value };
|
|
2527
|
+
}
|
|
2528
|
+
function startsWith(prefix) {
|
|
2529
|
+
return fixedMatcher("startsWith", prefix);
|
|
2530
|
+
}
|
|
2531
|
+
function endsWith(suffix) {
|
|
2532
|
+
return fixedMatcher("endsWith", suffix);
|
|
2533
|
+
}
|
|
2534
|
+
function matches(pattern) {
|
|
2535
|
+
if (!(pattern instanceof RegExp)) throw new TypeError("parseman: matches() requires a RegExp");
|
|
2536
|
+
if (pattern.flags.includes("g") || pattern.flags.includes("y")) {
|
|
2537
|
+
throw new TypeError("parseman: matches() does not accept global or sticky regex flags");
|
|
2538
|
+
}
|
|
2539
|
+
return { kind: "matches", value: pattern.source, flags: pattern.flags };
|
|
2540
|
+
}
|
|
2541
|
+
function when(key, parser2, opts = {}) {
|
|
2542
|
+
const usesRouted = parserUsesRouted(parser2);
|
|
2543
|
+
if (isStringMatcher(key)) {
|
|
2544
|
+
return { kind: "whenMatcher", matcher: key, parser: parser2, caseInsensitive: opts.caseInsensitive ?? false, usesRouted };
|
|
2545
|
+
}
|
|
2546
|
+
const keys = Array.isArray(key) ? [...key] : [key];
|
|
2547
|
+
if (keys.length === 0) throw new RangeError("parseman: when() requires at least one key");
|
|
2548
|
+
for (const item of keys) {
|
|
2549
|
+
if (typeof item !== "string") throw new TypeError("parseman: when() keys must be strings");
|
|
2550
|
+
}
|
|
2551
|
+
return { kind: "when", keys, parser: parser2, caseInsensitive: opts.caseInsensitive ?? false, usesRouted };
|
|
2552
|
+
}
|
|
2553
|
+
function otherwise(parser2) {
|
|
2554
|
+
return { kind: "otherwise", parser: parser2, usesRouted: parserUsesRouted(parser2) };
|
|
2555
|
+
}
|
|
2556
|
+
function routed() {
|
|
2557
|
+
return {
|
|
2558
|
+
_tag: "routed",
|
|
2559
|
+
_meta: { firstSet: { kind: "any" }, canMatchNewline: false, isTrivia: false },
|
|
2560
|
+
_def: { tag: "routed" },
|
|
2561
|
+
parse(_input, pos, ctx) {
|
|
2562
|
+
const item = ctx._routed;
|
|
2563
|
+
if (item === void 0) {
|
|
2564
|
+
return { ok: false, expected: ["routed()"], span: { start: pos, end: pos } };
|
|
2565
|
+
}
|
|
2566
|
+
if (pos !== item.span.start) {
|
|
2567
|
+
return { ok: false, expected: ["routed()"], span: { start: pos, end: pos } };
|
|
2568
|
+
}
|
|
2569
|
+
if (cstCaptureActive(ctx)) {
|
|
2570
|
+
pushCstLeaf(ctx, { _tag: "leaf", value: item.value, span: item.span });
|
|
2571
|
+
}
|
|
2572
|
+
return { ok: true, value: item.value, span: item.span };
|
|
2573
|
+
}
|
|
2574
|
+
};
|
|
2575
|
+
}
|
|
2576
|
+
function dispatch(selector, ...arms) {
|
|
2577
|
+
if (parserUsesRouted(selector)) {
|
|
2578
|
+
throw new Error("parseman: routed() can only appear inside a dispatch() branch");
|
|
2579
|
+
}
|
|
2580
|
+
let fallback;
|
|
2581
|
+
let fallbackUsesRouted = false;
|
|
2582
|
+
const matchers = [];
|
|
2583
|
+
const cases = [];
|
|
2584
|
+
const seen = [];
|
|
2585
|
+
for (let i = 0; i < arms.length; i++) {
|
|
2586
|
+
const arm = arms[i];
|
|
2587
|
+
if (arm.kind === "otherwise") {
|
|
2588
|
+
if (fallback !== void 0) throw new Error("parseman: dispatch() accepts at most one otherwise() arm");
|
|
2589
|
+
if (i !== arms.length - 1) throw new Error("parseman: otherwise() must be the last dispatch() arm");
|
|
2590
|
+
fallback = arm.parser;
|
|
2591
|
+
fallbackUsesRouted = arm.usesRouted === true;
|
|
2592
|
+
continue;
|
|
2593
|
+
}
|
|
2594
|
+
if (arm.kind === "whenMatcher") {
|
|
2595
|
+
const matcher = arm.matcher;
|
|
2596
|
+
matchers.push({
|
|
2597
|
+
kind: matcher.kind,
|
|
2598
|
+
value: matcher.value,
|
|
2599
|
+
...matcher.kind === "matches" ? { flags: matcher.flags } : {},
|
|
2600
|
+
parser: arm.parser,
|
|
2601
|
+
caseInsensitive: arm.caseInsensitive,
|
|
2602
|
+
...arm.usesRouted === true ? { usesRouted: true } : {}
|
|
2603
|
+
});
|
|
2604
|
+
continue;
|
|
2605
|
+
}
|
|
2606
|
+
for (const key of arm.keys) {
|
|
2607
|
+
const folded = asciiFoldKey(key);
|
|
2608
|
+
for (const prior of seen) {
|
|
2609
|
+
const overlaps = arm.caseInsensitive || prior.caseInsensitive ? folded === prior.folded : key === prior.raw;
|
|
2610
|
+
if (overlaps) throw new Error(`parseman: duplicate dispatch key ${JSON.stringify(key)}`);
|
|
2611
|
+
}
|
|
2612
|
+
seen.push({ raw: key, folded, caseInsensitive: arm.caseInsensitive });
|
|
2613
|
+
}
|
|
2614
|
+
cases.push({
|
|
2615
|
+
keys: arm.keys,
|
|
2616
|
+
parser: arm.parser,
|
|
2617
|
+
caseInsensitive: arm.caseInsensitive,
|
|
2618
|
+
...arm.usesRouted === true ? { usesRouted: true } : {}
|
|
2619
|
+
});
|
|
2620
|
+
}
|
|
2621
|
+
const meta = {
|
|
2622
|
+
firstSet: selector._meta.firstSet,
|
|
2623
|
+
canMatchNewline: selector._meta.canMatchNewline || cases.some((entry) => entry.parser._meta.canMatchNewline) || matchers.some((entry) => entry.parser._meta.canMatchNewline) || (fallback?._meta.canMatchNewline ?? false),
|
|
2624
|
+
isTrivia: false
|
|
2625
|
+
};
|
|
2626
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
2627
|
+
const byFoldedKey = /* @__PURE__ */ new Map();
|
|
2628
|
+
for (const entry of cases) {
|
|
2629
|
+
for (const key of entry.keys) {
|
|
2630
|
+
if (entry.caseInsensitive) byFoldedKey.set(asciiFoldKey(key), entry);
|
|
2631
|
+
else byKey.set(key, entry);
|
|
2632
|
+
}
|
|
2633
|
+
}
|
|
2634
|
+
return {
|
|
2635
|
+
_tag: "dispatch",
|
|
2636
|
+
_meta: meta,
|
|
2637
|
+
_def: {
|
|
2638
|
+
tag: "dispatch",
|
|
2639
|
+
selector,
|
|
2640
|
+
cases,
|
|
2641
|
+
...matchers.length === 0 ? {} : { matchers },
|
|
2642
|
+
...fallback === void 0 ? {} : { otherwise: fallback, ...fallbackUsesRouted ? { otherwiseUsesRouted: true } : {} }
|
|
2643
|
+
},
|
|
2644
|
+
parse(input, pos, ctx) {
|
|
2645
|
+
if (parserUsesRouted(selector)) {
|
|
2646
|
+
throw new Error("parseman: routed() can only appear inside a dispatch() branch");
|
|
2647
|
+
}
|
|
2648
|
+
const selectorMark = saveTriviaMark(ctx);
|
|
2649
|
+
const selected = selector.parse(input, pos, ctx);
|
|
2650
|
+
if (!selected.ok) return selected;
|
|
2651
|
+
const branch = byKey.get(selected.value) ?? (byFoldedKey.size === 0 ? void 0 : byFoldedKey.get(asciiFoldKey(selected.value))) ?? matchers.find((matcher) => matchesDispatchMatcher(selected.value, matcher)) ?? (fallback === void 0 ? void 0 : { keys: [], parser: fallback, caseInsensitive: false, usesRouted: fallbackUsesRouted });
|
|
2652
|
+
if (branch === void 0) {
|
|
2653
|
+
const expected = cases.flatMap((entry) => entry.keys.map((key) => JSON.stringify(key)));
|
|
2654
|
+
return { ok: false, expected, span: { start: selected.span.end, end: selected.span.end } };
|
|
2655
|
+
}
|
|
2656
|
+
const savedRouted = ctx._routed;
|
|
2657
|
+
const usesRouted = branchUsesRouted(branch);
|
|
2658
|
+
let mark = saveTriviaMark(ctx);
|
|
2659
|
+
if (usesRouted) {
|
|
2660
|
+
rollbackTrivia(ctx, selectorMark);
|
|
2661
|
+
mark = saveTriviaMark(ctx);
|
|
2662
|
+
ctx._routed = { value: selected.value, span: selected.span };
|
|
2663
|
+
}
|
|
2664
|
+
let result;
|
|
2665
|
+
try {
|
|
2666
|
+
result = branch.parser.parse(input, usesRouted ? pos : selected.span.end, ctx);
|
|
2667
|
+
} finally {
|
|
2668
|
+
if (usesRouted) ctx._routed = savedRouted;
|
|
2669
|
+
}
|
|
2670
|
+
if (!result.ok) {
|
|
2671
|
+
rollbackTrivia(ctx, mark);
|
|
2672
|
+
return { ...result, committed: true };
|
|
2673
|
+
}
|
|
2674
|
+
return {
|
|
2675
|
+
ok: true,
|
|
2676
|
+
value: [selected.value, result.value],
|
|
2677
|
+
span: { start: pos, end: result.span.end }
|
|
2678
|
+
};
|
|
2679
|
+
}
|
|
2680
|
+
};
|
|
2681
|
+
}
|
|
2682
|
+
|
|
2416
2683
|
// src/combinators/attempt.ts
|
|
2417
2684
|
function attempt(parser2) {
|
|
2418
2685
|
const guardable = parser2._meta.firstSet.kind !== "any" && !matchesEmpty(parser2);
|
|
@@ -2435,6 +2702,7 @@ function attempt(parser2) {
|
|
|
2435
2702
|
if (result.ok) return result;
|
|
2436
2703
|
rollbackCstCapture(ctx, mark);
|
|
2437
2704
|
if (log !== void 0 && ctx._triviaLog && ctx._triviaLog.length !== log) ctx._triviaLog.length = log;
|
|
2705
|
+
if (result.committed) return result;
|
|
2438
2706
|
return { ok: false, expected: result.expected, span: { start: pos, end: pos } };
|
|
2439
2707
|
}
|
|
2440
2708
|
};
|
|
@@ -2844,6 +3112,13 @@ function evalRuleMapIR(ir) {
|
|
|
2844
3112
|
"keywords",
|
|
2845
3113
|
"sequence",
|
|
2846
3114
|
"choice",
|
|
3115
|
+
"dispatch",
|
|
3116
|
+
"when",
|
|
3117
|
+
"startsWith",
|
|
3118
|
+
"endsWith",
|
|
3119
|
+
"matches",
|
|
3120
|
+
"otherwise",
|
|
3121
|
+
"routed",
|
|
2847
3122
|
"attempt",
|
|
2848
3123
|
"many",
|
|
2849
3124
|
"oneOrMore",
|
|
@@ -2877,6 +3152,13 @@ function evalRuleMapIR(ir) {
|
|
|
2877
3152
|
keywords,
|
|
2878
3153
|
sequence,
|
|
2879
3154
|
choice,
|
|
3155
|
+
dispatch,
|
|
3156
|
+
when,
|
|
3157
|
+
startsWith,
|
|
3158
|
+
endsWith,
|
|
3159
|
+
matches,
|
|
3160
|
+
otherwise,
|
|
3161
|
+
routed,
|
|
2880
3162
|
attempt,
|
|
2881
3163
|
many,
|
|
2882
3164
|
oneOrMore,
|
|
@@ -3071,6 +3353,15 @@ var Builder = class {
|
|
|
3071
3353
|
const items = def.parsers.map((p) => this.walk(p)).filter(nonEmpty);
|
|
3072
3354
|
return flattenChoice(items);
|
|
3073
3355
|
}
|
|
3356
|
+
case "dispatch": {
|
|
3357
|
+
const selector = this.walk(def.selector);
|
|
3358
|
+
const tails = [
|
|
3359
|
+
...def.cases.map((entry) => this.walk(entry.parser)),
|
|
3360
|
+
...def.matchers ? def.matchers.map((entry) => this.walk(entry.parser)) : [],
|
|
3361
|
+
...def.otherwise ? [this.walk(def.otherwise)] : []
|
|
3362
|
+
];
|
|
3363
|
+
return flattenSeq([selector, flattenChoice(tails)]);
|
|
3364
|
+
}
|
|
3074
3365
|
// `max`/`min` are carried through so a BOUNDED repeat does not render as an
|
|
3075
3366
|
// unbounded one — the spec is generated to be read as the truth about what
|
|
3076
3367
|
// parses, and `many(x, { min: 3, max: 8 })` is not `x+`.
|
|
@@ -3100,6 +3391,8 @@ var Builder = class {
|
|
|
3100
3391
|
return { kind: "not", item: this.walk(def.parser) };
|
|
3101
3392
|
case "peek":
|
|
3102
3393
|
return { kind: "peek", item: this.walk(def.parser) };
|
|
3394
|
+
case "routed":
|
|
3395
|
+
return { kind: "empty" };
|
|
3103
3396
|
// Transparent semantic wrappers — render the inner syntax.
|
|
3104
3397
|
case "transform":
|
|
3105
3398
|
case "token":
|
|
@@ -3257,7 +3550,7 @@ function render(node2) {
|
|
|
3257
3550
|
case "choice": {
|
|
3258
3551
|
const text = node2.items.map((it) => {
|
|
3259
3552
|
const r = render(it);
|
|
3260
|
-
return wrap(r.text, r.level, ALT);
|
|
3553
|
+
return r.text.length === 0 ? "/* empty */" : wrap(r.text, r.level, ALT);
|
|
3261
3554
|
}).join(" | ");
|
|
3262
3555
|
return { text, level: ALT };
|
|
3263
3556
|
}
|