parseman 0.39.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/gating.d.ts.map +1 -1
- package/dist/combinators/dispatch.d.ts +36 -3
- package/dist/combinators/dispatch.d.ts.map +1 -1
- package/dist/combinators/expect.d.ts.map +1 -1
- package/dist/combinators/first-set.d.ts.map +1 -1
- package/dist/compiler/codegen.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 +429 -42
- package/dist/index.cjs.map +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +429 -42
- package/dist/index.js.map +3 -3
- package/dist/plugin/evaluator.d.ts +6 -0
- package/dist/plugin/evaluator.d.ts.map +1 -1
- package/dist/plugin/index.cjs +446 -44
- package/dist/plugin/index.cjs.map +3 -3
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +446 -44
- package/dist/plugin/index.js.map +3 -3
- package/dist/spec/index.cjs +203 -17
- package/dist/spec/index.cjs.map +3 -3
- package/dist/spec/index.js +203 -17
- package/dist/spec/index.js.map +3 -3
- package/dist/spec/model.d.ts.map +1 -1
- package/dist/types.d.ts +20 -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("");
|
|
@@ -320,6 +322,7 @@ function markUnusedValues(root) {
|
|
|
320
322
|
case "dispatch":
|
|
321
323
|
visit(def.selector, consumed);
|
|
322
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);
|
|
323
326
|
if (def.otherwise) visit(def.otherwise, consumed);
|
|
324
327
|
return;
|
|
325
328
|
case "node":
|
|
@@ -773,6 +776,8 @@ function derive(c, seen) {
|
|
|
773
776
|
case "not":
|
|
774
777
|
case "peek":
|
|
775
778
|
return deriveExpected2(def.parser);
|
|
779
|
+
case "routed":
|
|
780
|
+
return ["routed()"];
|
|
776
781
|
case "lazy": {
|
|
777
782
|
const name = c._ruleName;
|
|
778
783
|
if (seen.has(c)) return name ? [name] : [];
|
|
@@ -2321,7 +2326,7 @@ function parserHasOwnFields(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
2321
2326
|
case "choice":
|
|
2322
2327
|
return d.parsers.some((x) => parserHasOwnFields(x, seen));
|
|
2323
2328
|
case "dispatch":
|
|
2324
|
-
return parserHasOwnFields(d.selector, seen) || d.cases.some((x) => parserHasOwnFields(x.parser, seen)) || (d.otherwise ? parserHasOwnFields(d.otherwise, seen) : false);
|
|
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);
|
|
2325
2330
|
case "sepBy":
|
|
2326
2331
|
return parserHasOwnFields(d.parser, seen) || parserHasOwnFields(d.separator, seen);
|
|
2327
2332
|
case "skip":
|
|
@@ -2437,42 +2442,195 @@ function keywords(words, opts = {}) {
|
|
|
2437
2442
|
}
|
|
2438
2443
|
|
|
2439
2444
|
// src/combinators/dispatch.ts
|
|
2440
|
-
function
|
|
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
|
+
}
|
|
2441
2546
|
const keys = Array.isArray(key) ? [...key] : [key];
|
|
2442
2547
|
if (keys.length === 0) throw new RangeError("parseman: when() requires at least one key");
|
|
2443
2548
|
for (const item of keys) {
|
|
2444
2549
|
if (typeof item !== "string") throw new TypeError("parseman: when() keys must be strings");
|
|
2445
2550
|
}
|
|
2446
|
-
return { kind: "when", keys, parser: parser2 };
|
|
2551
|
+
return { kind: "when", keys, parser: parser2, caseInsensitive: opts.caseInsensitive ?? false, usesRouted };
|
|
2447
2552
|
}
|
|
2448
2553
|
function otherwise(parser2) {
|
|
2449
|
-
return { kind: "otherwise", parser: 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
|
+
};
|
|
2450
2575
|
}
|
|
2451
2576
|
function dispatch(selector, ...arms) {
|
|
2577
|
+
if (parserUsesRouted(selector)) {
|
|
2578
|
+
throw new Error("parseman: routed() can only appear inside a dispatch() branch");
|
|
2579
|
+
}
|
|
2452
2580
|
let fallback;
|
|
2581
|
+
let fallbackUsesRouted = false;
|
|
2582
|
+
const matchers = [];
|
|
2453
2583
|
const cases = [];
|
|
2454
|
-
const seen =
|
|
2584
|
+
const seen = [];
|
|
2455
2585
|
for (let i = 0; i < arms.length; i++) {
|
|
2456
2586
|
const arm = arms[i];
|
|
2457
2587
|
if (arm.kind === "otherwise") {
|
|
2458
2588
|
if (fallback !== void 0) throw new Error("parseman: dispatch() accepts at most one otherwise() arm");
|
|
2459
2589
|
if (i !== arms.length - 1) throw new Error("parseman: otherwise() must be the last dispatch() arm");
|
|
2460
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
|
+
});
|
|
2461
2604
|
continue;
|
|
2462
2605
|
}
|
|
2463
2606
|
for (const key of arm.keys) {
|
|
2464
|
-
|
|
2465
|
-
seen
|
|
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 });
|
|
2466
2613
|
}
|
|
2467
|
-
cases.push({
|
|
2614
|
+
cases.push({
|
|
2615
|
+
keys: arm.keys,
|
|
2616
|
+
parser: arm.parser,
|
|
2617
|
+
caseInsensitive: arm.caseInsensitive,
|
|
2618
|
+
...arm.usesRouted === true ? { usesRouted: true } : {}
|
|
2619
|
+
});
|
|
2468
2620
|
}
|
|
2469
2621
|
const meta = {
|
|
2470
2622
|
firstSet: selector._meta.firstSet,
|
|
2471
|
-
canMatchNewline: selector._meta.canMatchNewline || cases.some((entry) => entry.parser._meta.canMatchNewline) || (fallback?._meta.canMatchNewline ?? false),
|
|
2623
|
+
canMatchNewline: selector._meta.canMatchNewline || cases.some((entry) => entry.parser._meta.canMatchNewline) || matchers.some((entry) => entry.parser._meta.canMatchNewline) || (fallback?._meta.canMatchNewline ?? false),
|
|
2472
2624
|
isTrivia: false
|
|
2473
2625
|
};
|
|
2474
2626
|
const byKey = /* @__PURE__ */ new Map();
|
|
2475
|
-
|
|
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
|
+
}
|
|
2476
2634
|
return {
|
|
2477
2635
|
_tag: "dispatch",
|
|
2478
2636
|
_meta: meta,
|
|
@@ -2480,18 +2638,35 @@ function dispatch(selector, ...arms) {
|
|
|
2480
2638
|
tag: "dispatch",
|
|
2481
2639
|
selector,
|
|
2482
2640
|
cases,
|
|
2483
|
-
...
|
|
2641
|
+
...matchers.length === 0 ? {} : { matchers },
|
|
2642
|
+
...fallback === void 0 ? {} : { otherwise: fallback, ...fallbackUsesRouted ? { otherwiseUsesRouted: true } : {} }
|
|
2484
2643
|
},
|
|
2485
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);
|
|
2486
2649
|
const selected = selector.parse(input, pos, ctx);
|
|
2487
2650
|
if (!selected.ok) return selected;
|
|
2488
|
-
const
|
|
2489
|
-
if (
|
|
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) {
|
|
2490
2653
|
const expected = cases.flatMap((entry) => entry.keys.map((key) => JSON.stringify(key)));
|
|
2491
2654
|
return { ok: false, expected, span: { start: selected.span.end, end: selected.span.end } };
|
|
2492
2655
|
}
|
|
2493
|
-
const
|
|
2494
|
-
const
|
|
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
|
+
}
|
|
2495
2670
|
if (!result.ok) {
|
|
2496
2671
|
rollbackTrivia(ctx, mark);
|
|
2497
2672
|
return { ...result, committed: true };
|
|
@@ -2939,7 +3114,11 @@ function evalRuleMapIR(ir) {
|
|
|
2939
3114
|
"choice",
|
|
2940
3115
|
"dispatch",
|
|
2941
3116
|
"when",
|
|
3117
|
+
"startsWith",
|
|
3118
|
+
"endsWith",
|
|
3119
|
+
"matches",
|
|
2942
3120
|
"otherwise",
|
|
3121
|
+
"routed",
|
|
2943
3122
|
"attempt",
|
|
2944
3123
|
"many",
|
|
2945
3124
|
"oneOrMore",
|
|
@@ -2975,7 +3154,11 @@ function evalRuleMapIR(ir) {
|
|
|
2975
3154
|
choice,
|
|
2976
3155
|
dispatch,
|
|
2977
3156
|
when,
|
|
3157
|
+
startsWith,
|
|
3158
|
+
endsWith,
|
|
3159
|
+
matches,
|
|
2978
3160
|
otherwise,
|
|
3161
|
+
routed,
|
|
2979
3162
|
attempt,
|
|
2980
3163
|
many,
|
|
2981
3164
|
oneOrMore,
|
|
@@ -3174,8 +3357,9 @@ var Builder = class {
|
|
|
3174
3357
|
const selector = this.walk(def.selector);
|
|
3175
3358
|
const tails = [
|
|
3176
3359
|
...def.cases.map((entry) => this.walk(entry.parser)),
|
|
3360
|
+
...def.matchers ? def.matchers.map((entry) => this.walk(entry.parser)) : [],
|
|
3177
3361
|
...def.otherwise ? [this.walk(def.otherwise)] : []
|
|
3178
|
-
]
|
|
3362
|
+
];
|
|
3179
3363
|
return flattenSeq([selector, flattenChoice(tails)]);
|
|
3180
3364
|
}
|
|
3181
3365
|
// `max`/`min` are carried through so a BOUNDED repeat does not render as an
|
|
@@ -3207,6 +3391,8 @@ var Builder = class {
|
|
|
3207
3391
|
return { kind: "not", item: this.walk(def.parser) };
|
|
3208
3392
|
case "peek":
|
|
3209
3393
|
return { kind: "peek", item: this.walk(def.parser) };
|
|
3394
|
+
case "routed":
|
|
3395
|
+
return { kind: "empty" };
|
|
3210
3396
|
// Transparent semantic wrappers — render the inner syntax.
|
|
3211
3397
|
case "transform":
|
|
3212
3398
|
case "token":
|
|
@@ -3364,7 +3550,7 @@ function render(node2) {
|
|
|
3364
3550
|
case "choice": {
|
|
3365
3551
|
const text = node2.items.map((it) => {
|
|
3366
3552
|
const r = render(it);
|
|
3367
|
-
return wrap(r.text, r.level, ALT);
|
|
3553
|
+
return r.text.length === 0 ? "/* empty */" : wrap(r.text, r.level, ALT);
|
|
3368
3554
|
}).join(" | ");
|
|
3369
3555
|
return { text, level: ALT };
|
|
3370
3556
|
}
|