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.js
CHANGED
|
@@ -48,6 +48,8 @@ function matchesEmpty(p, seen = /* @__PURE__ */ new Set(), resolve) {
|
|
|
48
48
|
return d.value.length === 0;
|
|
49
49
|
case "keywords":
|
|
50
50
|
return false;
|
|
51
|
+
case "routed":
|
|
52
|
+
return false;
|
|
51
53
|
case "regex":
|
|
52
54
|
try {
|
|
53
55
|
const m = new RegExp(d.source).exec("");
|
|
@@ -74,6 +76,8 @@ function matchesEmpty(p, seen = /* @__PURE__ */ new Set(), resolve) {
|
|
|
74
76
|
return d.parsers.every(me);
|
|
75
77
|
case "choice":
|
|
76
78
|
return d.parsers.some(me);
|
|
79
|
+
case "dispatch":
|
|
80
|
+
return me(d.selector);
|
|
77
81
|
case "transform":
|
|
78
82
|
case "label":
|
|
79
83
|
case "trivia":
|
|
@@ -147,6 +151,8 @@ function firstSetOf(p, seen = /* @__PURE__ */ new Set(), resolve) {
|
|
|
147
151
|
case "keywords":
|
|
148
152
|
return p._meta.firstSet;
|
|
149
153
|
// terminals: no refs, cached set is exact
|
|
154
|
+
case "dispatch":
|
|
155
|
+
return fs(d.selector);
|
|
150
156
|
case "lazy":
|
|
151
157
|
try {
|
|
152
158
|
return fs(d.thunk());
|
|
@@ -279,6 +285,12 @@ function markUnusedValues(root) {
|
|
|
279
285
|
case "choice":
|
|
280
286
|
for (const p of def.parsers) visit(p, consumed);
|
|
281
287
|
return;
|
|
288
|
+
case "dispatch":
|
|
289
|
+
visit(def.selector, consumed);
|
|
290
|
+
for (const entry of def.cases) visit(entry.parser, consumed);
|
|
291
|
+
if (def.matchers) for (const entry of def.matchers) visit(entry.parser, consumed);
|
|
292
|
+
if (def.otherwise) visit(def.otherwise, consumed);
|
|
293
|
+
return;
|
|
282
294
|
case "node":
|
|
283
295
|
visit(def.parser, false);
|
|
284
296
|
return;
|
|
@@ -689,6 +701,8 @@ function derive(c, seen) {
|
|
|
689
701
|
return [def.label];
|
|
690
702
|
case "choice":
|
|
691
703
|
return def.parsers.flatMap(deriveExpected2);
|
|
704
|
+
case "dispatch":
|
|
705
|
+
return deriveExpected2(def.selector);
|
|
692
706
|
// Union through the NULLABLE prefix, mirroring `sequenceFirstSet`. A leading
|
|
693
707
|
// `optional(…)`/`many(…)` can match nothing, so the term after it is equally
|
|
694
708
|
// able to fail first — deriving from term 0 alone named a token the parse
|
|
@@ -728,6 +742,8 @@ function derive(c, seen) {
|
|
|
728
742
|
case "not":
|
|
729
743
|
case "peek":
|
|
730
744
|
return deriveExpected2(def.parser);
|
|
745
|
+
case "routed":
|
|
746
|
+
return ["routed()"];
|
|
731
747
|
case "lazy": {
|
|
732
748
|
const name = c._ruleName;
|
|
733
749
|
if (seen.has(c)) return name ? [name] : [];
|
|
@@ -827,6 +843,7 @@ function choice(...args) {
|
|
|
827
843
|
const result = parsers[idx].parse(input, pos, ctx);
|
|
828
844
|
if (result.ok) return result;
|
|
829
845
|
expected.push(...result.expected);
|
|
846
|
+
if (result.committed) return { ok: false, expected, span: { start: pos, end: pos }, committed: true };
|
|
830
847
|
return { ok: false, expected, span: { start: pos, end: pos } };
|
|
831
848
|
}
|
|
832
849
|
return {
|
|
@@ -867,6 +884,7 @@ function choice(...args) {
|
|
|
867
884
|
rollbackCstCapture(ctx, mark);
|
|
868
885
|
if (logLen !== void 0 && ctx._triviaLog && ctx._triviaLog.length !== logLen) ctx._triviaLog.length = logLen;
|
|
869
886
|
expected.push(...result.expected);
|
|
887
|
+
if (result.committed) return { ok: false, expected, span: result.span, committed: true };
|
|
870
888
|
continue;
|
|
871
889
|
}
|
|
872
890
|
const checks = autoNot[i];
|
|
@@ -1817,6 +1835,7 @@ function repItem(combinator, input, cur, ctx, guardable) {
|
|
|
1817
1835
|
const result = combinator.parse(input, pos, ctx);
|
|
1818
1836
|
if (!result.ok) {
|
|
1819
1837
|
rollbackTrivia(ctx, mark);
|
|
1838
|
+
if (result.committed) return { fail: result, failPos: pos };
|
|
1820
1839
|
return { fail: result, failPos: pos };
|
|
1821
1840
|
}
|
|
1822
1841
|
if (result.span.end === pos) {
|
|
@@ -1860,6 +1879,7 @@ function many(combinator, opts = {}) {
|
|
|
1860
1879
|
const item = repItem(combinator, input, cur, ctx, guardable);
|
|
1861
1880
|
if (item === "stop") break;
|
|
1862
1881
|
if ("fail" in item) {
|
|
1882
|
+
if (!item.fail.ok && item.fail.committed) return item.fail;
|
|
1863
1883
|
const sync = ctx._tolerant ? ctx._sync : void 0;
|
|
1864
1884
|
if (sync === void 0) break;
|
|
1865
1885
|
if (matchesAt(sync, input, item.failPos, ctx)) break;
|
|
@@ -1904,6 +1924,7 @@ function atLeast(combinator, min, max) {
|
|
|
1904
1924
|
while (count < min) {
|
|
1905
1925
|
const item = repItem(combinator, input, cur, ctx, guardable);
|
|
1906
1926
|
if (item === "stop" || "fail" in item) {
|
|
1927
|
+
if (item !== "stop" && !item.fail.ok && item.fail.committed) return item.fail;
|
|
1907
1928
|
expected ??= deriveExpected(combinator);
|
|
1908
1929
|
return { ok: false, expected: expected.length > 0 ? expected : [combinator._tag], span: { start: cur, end: cur } };
|
|
1909
1930
|
}
|
|
@@ -1916,6 +1937,7 @@ function atLeast(combinator, min, max) {
|
|
|
1916
1937
|
const item = repItem(combinator, input, cur, ctx, guardable);
|
|
1917
1938
|
if (item === "stop") break;
|
|
1918
1939
|
if ("fail" in item) {
|
|
1940
|
+
if (!item.fail.ok && item.fail.committed) return item.fail;
|
|
1919
1941
|
const sync = ctx._tolerant ? ctx._sync : void 0;
|
|
1920
1942
|
if (sync === void 0) break;
|
|
1921
1943
|
if (matchesAt(sync, input, item.failPos, ctx)) break;
|
|
@@ -1954,6 +1976,7 @@ function optional(combinator) {
|
|
|
1954
1976
|
const result = combinator.parse(input, pos, ctx);
|
|
1955
1977
|
if (result.ok) return result;
|
|
1956
1978
|
rollbackTrivia(ctx, mark);
|
|
1979
|
+
if (result.committed) return result;
|
|
1957
1980
|
return { ok: true, value: null, span: { start: pos, end: pos } };
|
|
1958
1981
|
}
|
|
1959
1982
|
};
|
|
@@ -1990,6 +2013,7 @@ function sepBy(combinator, separator, opts = {}) {
|
|
|
1990
2013
|
values.push(first.value);
|
|
1991
2014
|
cur = first.span.end;
|
|
1992
2015
|
} else {
|
|
2016
|
+
if (first.committed) return first;
|
|
1993
2017
|
const term = ctx._tolerant ? ctx._sync : void 0;
|
|
1994
2018
|
if (term === void 0 || matchesAt(term, input, pos, ctx)) {
|
|
1995
2019
|
if (min >= 1) return failAt2(pos);
|
|
@@ -2017,6 +2041,7 @@ function sepBy(combinator, separator, opts = {}) {
|
|
|
2017
2041
|
const sep = separator.parse(input, sepPos, ctx);
|
|
2018
2042
|
if (!sep.ok) {
|
|
2019
2043
|
rollbackTrivia(ctx, loopMark);
|
|
2044
|
+
if (sep.committed) return sep;
|
|
2020
2045
|
break;
|
|
2021
2046
|
}
|
|
2022
2047
|
const sepMark = trailing === "forbid" ? void 0 : saveTriviaMark(ctx);
|
|
@@ -2032,6 +2057,7 @@ function sepBy(combinator, separator, opts = {}) {
|
|
|
2032
2057
|
}
|
|
2033
2058
|
const next = combinator.parse(input, nextPos, ctx);
|
|
2034
2059
|
if (!next.ok) {
|
|
2060
|
+
if (next.committed) return next;
|
|
2035
2061
|
const term = ctx._tolerant ? ctx._sync : void 0;
|
|
2036
2062
|
if (term !== void 0 && !matchesAt(term, input, nextPos, ctx)) {
|
|
2037
2063
|
expected ??= deriveExpected(combinator);
|
|
@@ -2265,6 +2291,8 @@ function parserHasOwnFields(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
2265
2291
|
case "sequence":
|
|
2266
2292
|
case "choice":
|
|
2267
2293
|
return d.parsers.some((x) => parserHasOwnFields(x, seen));
|
|
2294
|
+
case "dispatch":
|
|
2295
|
+
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);
|
|
2268
2296
|
case "sepBy":
|
|
2269
2297
|
return parserHasOwnFields(d.parser, seen) || parserHasOwnFields(d.separator, seen);
|
|
2270
2298
|
case "skip":
|
|
@@ -2379,6 +2407,245 @@ function keywords(words, opts = {}) {
|
|
|
2379
2407
|
};
|
|
2380
2408
|
}
|
|
2381
2409
|
|
|
2410
|
+
// src/combinators/dispatch.ts
|
|
2411
|
+
function parserUsesRouted(parser2, seen = /* @__PURE__ */ new Set()) {
|
|
2412
|
+
if (seen.has(parser2)) return false;
|
|
2413
|
+
seen.add(parser2);
|
|
2414
|
+
const def = parser2._def;
|
|
2415
|
+
switch (def.tag) {
|
|
2416
|
+
case "routed":
|
|
2417
|
+
return true;
|
|
2418
|
+
case "sequence":
|
|
2419
|
+
case "choice":
|
|
2420
|
+
return def.parsers.some((entry) => parserUsesRouted(entry, seen));
|
|
2421
|
+
case "dispatch":
|
|
2422
|
+
return false;
|
|
2423
|
+
case "sepBy":
|
|
2424
|
+
return parserUsesRouted(def.parser, seen) || parserUsesRouted(def.separator, seen);
|
|
2425
|
+
case "skip":
|
|
2426
|
+
return parserUsesRouted(def.main, seen) || parserUsesRouted(def.skipped, seen);
|
|
2427
|
+
case "grammar":
|
|
2428
|
+
return parserUsesRouted(def.parser, seen) || (def.triviaParser ? parserUsesRouted(def.triviaParser, seen) : false);
|
|
2429
|
+
case "scanTo":
|
|
2430
|
+
return parserUsesRouted(def.sentinel, seen) || def.skip.some((entry) => parserUsesRouted(entry, seen));
|
|
2431
|
+
case "recover":
|
|
2432
|
+
return parserUsesRouted(def.parser, seen) || parserUsesRouted(def.sentinel, seen);
|
|
2433
|
+
case "lazy":
|
|
2434
|
+
try {
|
|
2435
|
+
return parserUsesRouted(def.thunk(), seen);
|
|
2436
|
+
} catch {
|
|
2437
|
+
return false;
|
|
2438
|
+
}
|
|
2439
|
+
case "many":
|
|
2440
|
+
case "oneOrMore":
|
|
2441
|
+
case "optional":
|
|
2442
|
+
case "attempt":
|
|
2443
|
+
case "transform":
|
|
2444
|
+
case "trivia":
|
|
2445
|
+
case "token":
|
|
2446
|
+
case "leaf":
|
|
2447
|
+
case "label":
|
|
2448
|
+
case "field":
|
|
2449
|
+
case "not":
|
|
2450
|
+
case "peek":
|
|
2451
|
+
case "node":
|
|
2452
|
+
case "withCtx":
|
|
2453
|
+
case "expect":
|
|
2454
|
+
return parserUsesRouted(def.parser, seen);
|
|
2455
|
+
default:
|
|
2456
|
+
return false;
|
|
2457
|
+
}
|
|
2458
|
+
}
|
|
2459
|
+
function branchUsesRouted(branch) {
|
|
2460
|
+
return branch.usesRouted === true || parserUsesRouted(branch.parser);
|
|
2461
|
+
}
|
|
2462
|
+
function asciiFoldKey(key) {
|
|
2463
|
+
let out = "";
|
|
2464
|
+
for (let i = 0; i < key.length; i++) {
|
|
2465
|
+
const c = key.charCodeAt(i);
|
|
2466
|
+
out += String.fromCharCode(c >= 65 && c <= 90 ? c + 32 : c);
|
|
2467
|
+
}
|
|
2468
|
+
return out;
|
|
2469
|
+
}
|
|
2470
|
+
function matchesDispatchMatcher(value, matcher) {
|
|
2471
|
+
const candidate = matcher.caseInsensitive ? asciiFoldKey(value) : value;
|
|
2472
|
+
const expected = matcher.caseInsensitive ? asciiFoldKey(matcher.value) : matcher.value;
|
|
2473
|
+
switch (matcher.kind) {
|
|
2474
|
+
case "startsWith":
|
|
2475
|
+
return candidate.startsWith(expected);
|
|
2476
|
+
case "endsWith":
|
|
2477
|
+
return candidate.endsWith(expected);
|
|
2478
|
+
case "matches": {
|
|
2479
|
+
const flags = matcher.caseInsensitive && !matcher.flags?.includes("i") ? `${matcher.flags ?? ""}i` : matcher.flags ?? "";
|
|
2480
|
+
return new RegExp(matcher.value, flags).test(value);
|
|
2481
|
+
}
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2484
|
+
function isStringMatcher(value) {
|
|
2485
|
+
if (typeof value !== "object" || value === null) return false;
|
|
2486
|
+
const kind = value.kind;
|
|
2487
|
+
return kind === "startsWith" || kind === "endsWith" || kind === "matches";
|
|
2488
|
+
}
|
|
2489
|
+
function fixedMatcher(kind, value) {
|
|
2490
|
+
if (typeof value !== "string") throw new TypeError(`parseman: ${kind}() value must be a string`);
|
|
2491
|
+
if (value.length === 0) throw new RangeError(`parseman: ${kind}() requires a non-empty value`);
|
|
2492
|
+
return { kind, value };
|
|
2493
|
+
}
|
|
2494
|
+
function startsWith(prefix) {
|
|
2495
|
+
return fixedMatcher("startsWith", prefix);
|
|
2496
|
+
}
|
|
2497
|
+
function endsWith(suffix) {
|
|
2498
|
+
return fixedMatcher("endsWith", suffix);
|
|
2499
|
+
}
|
|
2500
|
+
function matches(pattern) {
|
|
2501
|
+
if (!(pattern instanceof RegExp)) throw new TypeError("parseman: matches() requires a RegExp");
|
|
2502
|
+
if (pattern.flags.includes("g") || pattern.flags.includes("y")) {
|
|
2503
|
+
throw new TypeError("parseman: matches() does not accept global or sticky regex flags");
|
|
2504
|
+
}
|
|
2505
|
+
return { kind: "matches", value: pattern.source, flags: pattern.flags };
|
|
2506
|
+
}
|
|
2507
|
+
function when(key, parser2, opts = {}) {
|
|
2508
|
+
const usesRouted = parserUsesRouted(parser2);
|
|
2509
|
+
if (isStringMatcher(key)) {
|
|
2510
|
+
return { kind: "whenMatcher", matcher: key, parser: parser2, caseInsensitive: opts.caseInsensitive ?? false, usesRouted };
|
|
2511
|
+
}
|
|
2512
|
+
const keys = Array.isArray(key) ? [...key] : [key];
|
|
2513
|
+
if (keys.length === 0) throw new RangeError("parseman: when() requires at least one key");
|
|
2514
|
+
for (const item of keys) {
|
|
2515
|
+
if (typeof item !== "string") throw new TypeError("parseman: when() keys must be strings");
|
|
2516
|
+
}
|
|
2517
|
+
return { kind: "when", keys, parser: parser2, caseInsensitive: opts.caseInsensitive ?? false, usesRouted };
|
|
2518
|
+
}
|
|
2519
|
+
function otherwise(parser2) {
|
|
2520
|
+
return { kind: "otherwise", parser: parser2, usesRouted: parserUsesRouted(parser2) };
|
|
2521
|
+
}
|
|
2522
|
+
function routed() {
|
|
2523
|
+
return {
|
|
2524
|
+
_tag: "routed",
|
|
2525
|
+
_meta: { firstSet: { kind: "any" }, canMatchNewline: false, isTrivia: false },
|
|
2526
|
+
_def: { tag: "routed" },
|
|
2527
|
+
parse(_input, pos, ctx) {
|
|
2528
|
+
const item = ctx._routed;
|
|
2529
|
+
if (item === void 0) {
|
|
2530
|
+
return { ok: false, expected: ["routed()"], span: { start: pos, end: pos } };
|
|
2531
|
+
}
|
|
2532
|
+
if (pos !== item.span.start) {
|
|
2533
|
+
return { ok: false, expected: ["routed()"], span: { start: pos, end: pos } };
|
|
2534
|
+
}
|
|
2535
|
+
if (cstCaptureActive(ctx)) {
|
|
2536
|
+
pushCstLeaf(ctx, { _tag: "leaf", value: item.value, span: item.span });
|
|
2537
|
+
}
|
|
2538
|
+
return { ok: true, value: item.value, span: item.span };
|
|
2539
|
+
}
|
|
2540
|
+
};
|
|
2541
|
+
}
|
|
2542
|
+
function dispatch(selector, ...arms) {
|
|
2543
|
+
if (parserUsesRouted(selector)) {
|
|
2544
|
+
throw new Error("parseman: routed() can only appear inside a dispatch() branch");
|
|
2545
|
+
}
|
|
2546
|
+
let fallback;
|
|
2547
|
+
let fallbackUsesRouted = false;
|
|
2548
|
+
const matchers = [];
|
|
2549
|
+
const cases = [];
|
|
2550
|
+
const seen = [];
|
|
2551
|
+
for (let i = 0; i < arms.length; i++) {
|
|
2552
|
+
const arm = arms[i];
|
|
2553
|
+
if (arm.kind === "otherwise") {
|
|
2554
|
+
if (fallback !== void 0) throw new Error("parseman: dispatch() accepts at most one otherwise() arm");
|
|
2555
|
+
if (i !== arms.length - 1) throw new Error("parseman: otherwise() must be the last dispatch() arm");
|
|
2556
|
+
fallback = arm.parser;
|
|
2557
|
+
fallbackUsesRouted = arm.usesRouted === true;
|
|
2558
|
+
continue;
|
|
2559
|
+
}
|
|
2560
|
+
if (arm.kind === "whenMatcher") {
|
|
2561
|
+
const matcher = arm.matcher;
|
|
2562
|
+
matchers.push({
|
|
2563
|
+
kind: matcher.kind,
|
|
2564
|
+
value: matcher.value,
|
|
2565
|
+
...matcher.kind === "matches" ? { flags: matcher.flags } : {},
|
|
2566
|
+
parser: arm.parser,
|
|
2567
|
+
caseInsensitive: arm.caseInsensitive,
|
|
2568
|
+
...arm.usesRouted === true ? { usesRouted: true } : {}
|
|
2569
|
+
});
|
|
2570
|
+
continue;
|
|
2571
|
+
}
|
|
2572
|
+
for (const key of arm.keys) {
|
|
2573
|
+
const folded = asciiFoldKey(key);
|
|
2574
|
+
for (const prior of seen) {
|
|
2575
|
+
const overlaps = arm.caseInsensitive || prior.caseInsensitive ? folded === prior.folded : key === prior.raw;
|
|
2576
|
+
if (overlaps) throw new Error(`parseman: duplicate dispatch key ${JSON.stringify(key)}`);
|
|
2577
|
+
}
|
|
2578
|
+
seen.push({ raw: key, folded, caseInsensitive: arm.caseInsensitive });
|
|
2579
|
+
}
|
|
2580
|
+
cases.push({
|
|
2581
|
+
keys: arm.keys,
|
|
2582
|
+
parser: arm.parser,
|
|
2583
|
+
caseInsensitive: arm.caseInsensitive,
|
|
2584
|
+
...arm.usesRouted === true ? { usesRouted: true } : {}
|
|
2585
|
+
});
|
|
2586
|
+
}
|
|
2587
|
+
const meta = {
|
|
2588
|
+
firstSet: selector._meta.firstSet,
|
|
2589
|
+
canMatchNewline: selector._meta.canMatchNewline || cases.some((entry) => entry.parser._meta.canMatchNewline) || matchers.some((entry) => entry.parser._meta.canMatchNewline) || (fallback?._meta.canMatchNewline ?? false),
|
|
2590
|
+
isTrivia: false
|
|
2591
|
+
};
|
|
2592
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
2593
|
+
const byFoldedKey = /* @__PURE__ */ new Map();
|
|
2594
|
+
for (const entry of cases) {
|
|
2595
|
+
for (const key of entry.keys) {
|
|
2596
|
+
if (entry.caseInsensitive) byFoldedKey.set(asciiFoldKey(key), entry);
|
|
2597
|
+
else byKey.set(key, entry);
|
|
2598
|
+
}
|
|
2599
|
+
}
|
|
2600
|
+
return {
|
|
2601
|
+
_tag: "dispatch",
|
|
2602
|
+
_meta: meta,
|
|
2603
|
+
_def: {
|
|
2604
|
+
tag: "dispatch",
|
|
2605
|
+
selector,
|
|
2606
|
+
cases,
|
|
2607
|
+
...matchers.length === 0 ? {} : { matchers },
|
|
2608
|
+
...fallback === void 0 ? {} : { otherwise: fallback, ...fallbackUsesRouted ? { otherwiseUsesRouted: true } : {} }
|
|
2609
|
+
},
|
|
2610
|
+
parse(input, pos, ctx) {
|
|
2611
|
+
if (parserUsesRouted(selector)) {
|
|
2612
|
+
throw new Error("parseman: routed() can only appear inside a dispatch() branch");
|
|
2613
|
+
}
|
|
2614
|
+
const selectorMark = saveTriviaMark(ctx);
|
|
2615
|
+
const selected = selector.parse(input, pos, ctx);
|
|
2616
|
+
if (!selected.ok) return selected;
|
|
2617
|
+
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 });
|
|
2618
|
+
if (branch === void 0) {
|
|
2619
|
+
const expected = cases.flatMap((entry) => entry.keys.map((key) => JSON.stringify(key)));
|
|
2620
|
+
return { ok: false, expected, span: { start: selected.span.end, end: selected.span.end } };
|
|
2621
|
+
}
|
|
2622
|
+
const savedRouted = ctx._routed;
|
|
2623
|
+
const usesRouted = branchUsesRouted(branch);
|
|
2624
|
+
let mark = saveTriviaMark(ctx);
|
|
2625
|
+
if (usesRouted) {
|
|
2626
|
+
rollbackTrivia(ctx, selectorMark);
|
|
2627
|
+
mark = saveTriviaMark(ctx);
|
|
2628
|
+
ctx._routed = { value: selected.value, span: selected.span };
|
|
2629
|
+
}
|
|
2630
|
+
let result;
|
|
2631
|
+
try {
|
|
2632
|
+
result = branch.parser.parse(input, usesRouted ? pos : selected.span.end, ctx);
|
|
2633
|
+
} finally {
|
|
2634
|
+
if (usesRouted) ctx._routed = savedRouted;
|
|
2635
|
+
}
|
|
2636
|
+
if (!result.ok) {
|
|
2637
|
+
rollbackTrivia(ctx, mark);
|
|
2638
|
+
return { ...result, committed: true };
|
|
2639
|
+
}
|
|
2640
|
+
return {
|
|
2641
|
+
ok: true,
|
|
2642
|
+
value: [selected.value, result.value],
|
|
2643
|
+
span: { start: pos, end: result.span.end }
|
|
2644
|
+
};
|
|
2645
|
+
}
|
|
2646
|
+
};
|
|
2647
|
+
}
|
|
2648
|
+
|
|
2382
2649
|
// src/combinators/attempt.ts
|
|
2383
2650
|
function attempt(parser2) {
|
|
2384
2651
|
const guardable = parser2._meta.firstSet.kind !== "any" && !matchesEmpty(parser2);
|
|
@@ -2401,6 +2668,7 @@ function attempt(parser2) {
|
|
|
2401
2668
|
if (result.ok) return result;
|
|
2402
2669
|
rollbackCstCapture(ctx, mark);
|
|
2403
2670
|
if (log !== void 0 && ctx._triviaLog && ctx._triviaLog.length !== log) ctx._triviaLog.length = log;
|
|
2671
|
+
if (result.committed) return result;
|
|
2404
2672
|
return { ok: false, expected: result.expected, span: { start: pos, end: pos } };
|
|
2405
2673
|
}
|
|
2406
2674
|
};
|
|
@@ -2810,6 +3078,13 @@ function evalRuleMapIR(ir) {
|
|
|
2810
3078
|
"keywords",
|
|
2811
3079
|
"sequence",
|
|
2812
3080
|
"choice",
|
|
3081
|
+
"dispatch",
|
|
3082
|
+
"when",
|
|
3083
|
+
"startsWith",
|
|
3084
|
+
"endsWith",
|
|
3085
|
+
"matches",
|
|
3086
|
+
"otherwise",
|
|
3087
|
+
"routed",
|
|
2813
3088
|
"attempt",
|
|
2814
3089
|
"many",
|
|
2815
3090
|
"oneOrMore",
|
|
@@ -2843,6 +3118,13 @@ function evalRuleMapIR(ir) {
|
|
|
2843
3118
|
keywords,
|
|
2844
3119
|
sequence,
|
|
2845
3120
|
choice,
|
|
3121
|
+
dispatch,
|
|
3122
|
+
when,
|
|
3123
|
+
startsWith,
|
|
3124
|
+
endsWith,
|
|
3125
|
+
matches,
|
|
3126
|
+
otherwise,
|
|
3127
|
+
routed,
|
|
2846
3128
|
attempt,
|
|
2847
3129
|
many,
|
|
2848
3130
|
oneOrMore,
|
|
@@ -3037,6 +3319,15 @@ var Builder = class {
|
|
|
3037
3319
|
const items = def.parsers.map((p) => this.walk(p)).filter(nonEmpty);
|
|
3038
3320
|
return flattenChoice(items);
|
|
3039
3321
|
}
|
|
3322
|
+
case "dispatch": {
|
|
3323
|
+
const selector = this.walk(def.selector);
|
|
3324
|
+
const tails = [
|
|
3325
|
+
...def.cases.map((entry) => this.walk(entry.parser)),
|
|
3326
|
+
...def.matchers ? def.matchers.map((entry) => this.walk(entry.parser)) : [],
|
|
3327
|
+
...def.otherwise ? [this.walk(def.otherwise)] : []
|
|
3328
|
+
];
|
|
3329
|
+
return flattenSeq([selector, flattenChoice(tails)]);
|
|
3330
|
+
}
|
|
3040
3331
|
// `max`/`min` are carried through so a BOUNDED repeat does not render as an
|
|
3041
3332
|
// unbounded one — the spec is generated to be read as the truth about what
|
|
3042
3333
|
// parses, and `many(x, { min: 3, max: 8 })` is not `x+`.
|
|
@@ -3066,6 +3357,8 @@ var Builder = class {
|
|
|
3066
3357
|
return { kind: "not", item: this.walk(def.parser) };
|
|
3067
3358
|
case "peek":
|
|
3068
3359
|
return { kind: "peek", item: this.walk(def.parser) };
|
|
3360
|
+
case "routed":
|
|
3361
|
+
return { kind: "empty" };
|
|
3069
3362
|
// Transparent semantic wrappers — render the inner syntax.
|
|
3070
3363
|
case "transform":
|
|
3071
3364
|
case "token":
|
|
@@ -3223,7 +3516,7 @@ function render(node2) {
|
|
|
3223
3516
|
case "choice": {
|
|
3224
3517
|
const text = node2.items.map((it) => {
|
|
3225
3518
|
const r = render(it);
|
|
3226
|
-
return wrap(r.text, r.level, ALT);
|
|
3519
|
+
return r.text.length === 0 ? "/* empty */" : wrap(r.text, r.level, ALT);
|
|
3227
3520
|
}).join(" | ");
|
|
3228
3521
|
return { text, level: ALT };
|
|
3229
3522
|
}
|