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.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("");
|
|
@@ -286,6 +288,7 @@ function markUnusedValues(root) {
|
|
|
286
288
|
case "dispatch":
|
|
287
289
|
visit(def.selector, consumed);
|
|
288
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);
|
|
289
292
|
if (def.otherwise) visit(def.otherwise, consumed);
|
|
290
293
|
return;
|
|
291
294
|
case "node":
|
|
@@ -739,6 +742,8 @@ function derive(c, seen) {
|
|
|
739
742
|
case "not":
|
|
740
743
|
case "peek":
|
|
741
744
|
return deriveExpected2(def.parser);
|
|
745
|
+
case "routed":
|
|
746
|
+
return ["routed()"];
|
|
742
747
|
case "lazy": {
|
|
743
748
|
const name = c._ruleName;
|
|
744
749
|
if (seen.has(c)) return name ? [name] : [];
|
|
@@ -2287,7 +2292,7 @@ function parserHasOwnFields(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
2287
2292
|
case "choice":
|
|
2288
2293
|
return d.parsers.some((x) => parserHasOwnFields(x, seen));
|
|
2289
2294
|
case "dispatch":
|
|
2290
|
-
return parserHasOwnFields(d.selector, seen) || d.cases.some((x) => parserHasOwnFields(x.parser, seen)) || (d.otherwise ? parserHasOwnFields(d.otherwise, seen) : false);
|
|
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);
|
|
2291
2296
|
case "sepBy":
|
|
2292
2297
|
return parserHasOwnFields(d.parser, seen) || parserHasOwnFields(d.separator, seen);
|
|
2293
2298
|
case "skip":
|
|
@@ -2403,42 +2408,195 @@ function keywords(words, opts = {}) {
|
|
|
2403
2408
|
}
|
|
2404
2409
|
|
|
2405
2410
|
// src/combinators/dispatch.ts
|
|
2406
|
-
function
|
|
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
|
+
}
|
|
2407
2512
|
const keys = Array.isArray(key) ? [...key] : [key];
|
|
2408
2513
|
if (keys.length === 0) throw new RangeError("parseman: when() requires at least one key");
|
|
2409
2514
|
for (const item of keys) {
|
|
2410
2515
|
if (typeof item !== "string") throw new TypeError("parseman: when() keys must be strings");
|
|
2411
2516
|
}
|
|
2412
|
-
return { kind: "when", keys, parser: parser2 };
|
|
2517
|
+
return { kind: "when", keys, parser: parser2, caseInsensitive: opts.caseInsensitive ?? false, usesRouted };
|
|
2413
2518
|
}
|
|
2414
2519
|
function otherwise(parser2) {
|
|
2415
|
-
return { kind: "otherwise", parser: 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
|
+
};
|
|
2416
2541
|
}
|
|
2417
2542
|
function dispatch(selector, ...arms) {
|
|
2543
|
+
if (parserUsesRouted(selector)) {
|
|
2544
|
+
throw new Error("parseman: routed() can only appear inside a dispatch() branch");
|
|
2545
|
+
}
|
|
2418
2546
|
let fallback;
|
|
2547
|
+
let fallbackUsesRouted = false;
|
|
2548
|
+
const matchers = [];
|
|
2419
2549
|
const cases = [];
|
|
2420
|
-
const seen =
|
|
2550
|
+
const seen = [];
|
|
2421
2551
|
for (let i = 0; i < arms.length; i++) {
|
|
2422
2552
|
const arm = arms[i];
|
|
2423
2553
|
if (arm.kind === "otherwise") {
|
|
2424
2554
|
if (fallback !== void 0) throw new Error("parseman: dispatch() accepts at most one otherwise() arm");
|
|
2425
2555
|
if (i !== arms.length - 1) throw new Error("parseman: otherwise() must be the last dispatch() arm");
|
|
2426
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
|
+
});
|
|
2427
2570
|
continue;
|
|
2428
2571
|
}
|
|
2429
2572
|
for (const key of arm.keys) {
|
|
2430
|
-
|
|
2431
|
-
seen
|
|
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 });
|
|
2432
2579
|
}
|
|
2433
|
-
cases.push({
|
|
2580
|
+
cases.push({
|
|
2581
|
+
keys: arm.keys,
|
|
2582
|
+
parser: arm.parser,
|
|
2583
|
+
caseInsensitive: arm.caseInsensitive,
|
|
2584
|
+
...arm.usesRouted === true ? { usesRouted: true } : {}
|
|
2585
|
+
});
|
|
2434
2586
|
}
|
|
2435
2587
|
const meta = {
|
|
2436
2588
|
firstSet: selector._meta.firstSet,
|
|
2437
|
-
canMatchNewline: selector._meta.canMatchNewline || cases.some((entry) => entry.parser._meta.canMatchNewline) || (fallback?._meta.canMatchNewline ?? false),
|
|
2589
|
+
canMatchNewline: selector._meta.canMatchNewline || cases.some((entry) => entry.parser._meta.canMatchNewline) || matchers.some((entry) => entry.parser._meta.canMatchNewline) || (fallback?._meta.canMatchNewline ?? false),
|
|
2438
2590
|
isTrivia: false
|
|
2439
2591
|
};
|
|
2440
2592
|
const byKey = /* @__PURE__ */ new Map();
|
|
2441
|
-
|
|
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
|
+
}
|
|
2442
2600
|
return {
|
|
2443
2601
|
_tag: "dispatch",
|
|
2444
2602
|
_meta: meta,
|
|
@@ -2446,18 +2604,35 @@ function dispatch(selector, ...arms) {
|
|
|
2446
2604
|
tag: "dispatch",
|
|
2447
2605
|
selector,
|
|
2448
2606
|
cases,
|
|
2449
|
-
...
|
|
2607
|
+
...matchers.length === 0 ? {} : { matchers },
|
|
2608
|
+
...fallback === void 0 ? {} : { otherwise: fallback, ...fallbackUsesRouted ? { otherwiseUsesRouted: true } : {} }
|
|
2450
2609
|
},
|
|
2451
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);
|
|
2452
2615
|
const selected = selector.parse(input, pos, ctx);
|
|
2453
2616
|
if (!selected.ok) return selected;
|
|
2454
|
-
const
|
|
2455
|
-
if (
|
|
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) {
|
|
2456
2619
|
const expected = cases.flatMap((entry) => entry.keys.map((key) => JSON.stringify(key)));
|
|
2457
2620
|
return { ok: false, expected, span: { start: selected.span.end, end: selected.span.end } };
|
|
2458
2621
|
}
|
|
2459
|
-
const
|
|
2460
|
-
const
|
|
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
|
+
}
|
|
2461
2636
|
if (!result.ok) {
|
|
2462
2637
|
rollbackTrivia(ctx, mark);
|
|
2463
2638
|
return { ...result, committed: true };
|
|
@@ -2905,7 +3080,11 @@ function evalRuleMapIR(ir) {
|
|
|
2905
3080
|
"choice",
|
|
2906
3081
|
"dispatch",
|
|
2907
3082
|
"when",
|
|
3083
|
+
"startsWith",
|
|
3084
|
+
"endsWith",
|
|
3085
|
+
"matches",
|
|
2908
3086
|
"otherwise",
|
|
3087
|
+
"routed",
|
|
2909
3088
|
"attempt",
|
|
2910
3089
|
"many",
|
|
2911
3090
|
"oneOrMore",
|
|
@@ -2941,7 +3120,11 @@ function evalRuleMapIR(ir) {
|
|
|
2941
3120
|
choice,
|
|
2942
3121
|
dispatch,
|
|
2943
3122
|
when,
|
|
3123
|
+
startsWith,
|
|
3124
|
+
endsWith,
|
|
3125
|
+
matches,
|
|
2944
3126
|
otherwise,
|
|
3127
|
+
routed,
|
|
2945
3128
|
attempt,
|
|
2946
3129
|
many,
|
|
2947
3130
|
oneOrMore,
|
|
@@ -3140,8 +3323,9 @@ var Builder = class {
|
|
|
3140
3323
|
const selector = this.walk(def.selector);
|
|
3141
3324
|
const tails = [
|
|
3142
3325
|
...def.cases.map((entry) => this.walk(entry.parser)),
|
|
3326
|
+
...def.matchers ? def.matchers.map((entry) => this.walk(entry.parser)) : [],
|
|
3143
3327
|
...def.otherwise ? [this.walk(def.otherwise)] : []
|
|
3144
|
-
]
|
|
3328
|
+
];
|
|
3145
3329
|
return flattenSeq([selector, flattenChoice(tails)]);
|
|
3146
3330
|
}
|
|
3147
3331
|
// `max`/`min` are carried through so a BOUNDED repeat does not render as an
|
|
@@ -3173,6 +3357,8 @@ var Builder = class {
|
|
|
3173
3357
|
return { kind: "not", item: this.walk(def.parser) };
|
|
3174
3358
|
case "peek":
|
|
3175
3359
|
return { kind: "peek", item: this.walk(def.parser) };
|
|
3360
|
+
case "routed":
|
|
3361
|
+
return { kind: "empty" };
|
|
3176
3362
|
// Transparent semantic wrappers — render the inner syntax.
|
|
3177
3363
|
case "transform":
|
|
3178
3364
|
case "token":
|
|
@@ -3330,7 +3516,7 @@ function render(node2) {
|
|
|
3330
3516
|
case "choice": {
|
|
3331
3517
|
const text = node2.items.map((it) => {
|
|
3332
3518
|
const r = render(it);
|
|
3333
|
-
return wrap(r.text, r.level, ALT);
|
|
3519
|
+
return r.text.length === 0 ? "/* empty */" : wrap(r.text, r.level, ALT);
|
|
3334
3520
|
}).join(" | ");
|
|
3335
3521
|
return { text, level: ALT };
|
|
3336
3522
|
}
|