parseman 0.32.0 → 0.34.0
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 +66 -1
- package/dist/analysis/gating.d.ts.map +1 -1
- package/dist/combinators/case-fold.d.ts +39 -0
- package/dist/combinators/case-fold.d.ts.map +1 -0
- package/dist/combinators/expect.d.ts.map +1 -1
- package/dist/combinators/first-set.d.ts +38 -11
- package/dist/combinators/first-set.d.ts.map +1 -1
- package/dist/combinators/keywords.d.ts +12 -3
- package/dist/combinators/keywords.d.ts.map +1 -1
- package/dist/combinators/not.d.ts.map +1 -1
- package/dist/combinators/peek.d.ts +27 -0
- package/dist/combinators/peek.d.ts.map +1 -0
- package/dist/combinators/repeat.d.ts +68 -3
- package/dist/combinators/repeat.d.ts.map +1 -1
- package/dist/combinators/trivia-skip.d.ts +23 -1
- package/dist/combinators/trivia-skip.d.ts.map +1 -1
- package/dist/compiler/codegen.d.ts +40 -0
- package/dist/compiler/codegen.d.ts.map +1 -1
- package/dist/compiler/fields.d.ts.map +1 -1
- package/dist/compiler/ir-serialize.d.ts +1 -1
- package/dist/compiler/ir-serialize.d.ts.map +1 -1
- package/dist/compiler/linker.d.ts +5 -0
- package/dist/compiler/linker.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 +459 -86
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +459 -86
- package/dist/index.js.map +4 -4
- package/dist/language-service/index.cjs.map +1 -1
- package/dist/language-service/index.js.map +1 -1
- package/dist/plugin/evaluator.d.ts.map +1 -1
- package/dist/plugin/index.cjs +538 -123
- package/dist/plugin/index.cjs.map +4 -4
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +538 -123
- package/dist/plugin/index.js.map +4 -4
- package/dist/spec/ebnf.d.ts.map +1 -1
- package/dist/spec/index.cjs +79 -17
- package/dist/spec/index.cjs.map +2 -2
- package/dist/spec/index.js +79 -17
- package/dist/spec/index.js.map +2 -2
- package/dist/spec/model.d.ts +23 -3
- package/dist/spec/model.d.ts.map +1 -1
- package/dist/spec/railroad.d.ts.map +1 -1
- package/dist/types.d.ts +9 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +5 -1
package/dist/plugin/index.js
CHANGED
|
@@ -7,6 +7,11 @@ import { ResolverFactory } from "oxc-resolver";
|
|
|
7
7
|
import MagicString from "magic-string";
|
|
8
8
|
|
|
9
9
|
// src/combinators/first-set.ts
|
|
10
|
+
function resolveNamedRef(p, resolve2) {
|
|
11
|
+
if (resolve2 === void 0) return void 0;
|
|
12
|
+
const name = p._ruleName;
|
|
13
|
+
return name === void 0 ? void 0 : resolve2(name);
|
|
14
|
+
}
|
|
10
15
|
function union(a, b) {
|
|
11
16
|
if (a.kind === "any" || b.kind === "any") return { kind: "any" };
|
|
12
17
|
if (a.kind === "empty") return b;
|
|
@@ -44,10 +49,10 @@ function any() {
|
|
|
44
49
|
function empty() {
|
|
45
50
|
return { kind: "empty" };
|
|
46
51
|
}
|
|
47
|
-
function matchesEmpty(p, seen = /* @__PURE__ */ new Set()) {
|
|
52
|
+
function matchesEmpty(p, seen = /* @__PURE__ */ new Set(), resolve2) {
|
|
48
53
|
if (seen.has(p)) return true;
|
|
49
54
|
seen.add(p);
|
|
50
|
-
const me = (c) => matchesEmpty(c, seen);
|
|
55
|
+
const me = (c) => matchesEmpty(c, seen, resolve2);
|
|
51
56
|
const d = p._def;
|
|
52
57
|
switch (d.tag) {
|
|
53
58
|
case "literal":
|
|
@@ -64,8 +69,16 @@ function matchesEmpty(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
64
69
|
case "many":
|
|
65
70
|
case "optional":
|
|
66
71
|
case "not":
|
|
72
|
+
case "peek":
|
|
67
73
|
return true;
|
|
68
74
|
// zero repetitions / absent / lookahead
|
|
75
|
+
// Default `sepBy` is `(item (sep item)*)?` — it MATCHES THE EMPTY STRING.
|
|
76
|
+
// Any `min >= 1` requires that many ITEMS, so it is nullable only when the
|
|
77
|
+
// item is. (Keying this off `min === 1` reported every `{ min: 2 }` list as
|
|
78
|
+
// nullable — safe, but wrong, and it put a bogus `nullable-prefix` note on
|
|
79
|
+
// the gating diagnostic for a list that can never match empty.)
|
|
80
|
+
case "sepBy":
|
|
81
|
+
return d.min >= 1 ? me(d.parser) : true;
|
|
69
82
|
case "oneOrMore":
|
|
70
83
|
return me(d.parser);
|
|
71
84
|
case "sequence":
|
|
@@ -89,7 +102,8 @@ function matchesEmpty(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
89
102
|
try {
|
|
90
103
|
return me(d.thunk());
|
|
91
104
|
} catch {
|
|
92
|
-
|
|
105
|
+
const t = resolveNamedRef(p, resolve2);
|
|
106
|
+
return t ? me(t) : true;
|
|
93
107
|
}
|
|
94
108
|
default:
|
|
95
109
|
return true;
|
|
@@ -98,18 +112,45 @@ function matchesEmpty(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
98
112
|
function isZeroWidthAssertion(p) {
|
|
99
113
|
return p._def.tag === "not";
|
|
100
114
|
}
|
|
115
|
+
function isPositiveLookahead(p) {
|
|
116
|
+
return p._def.tag === "peek";
|
|
117
|
+
}
|
|
118
|
+
function narrowBy(acc, constraint) {
|
|
119
|
+
if (constraint.kind === "any") return acc;
|
|
120
|
+
if (acc === null) return constraint;
|
|
121
|
+
if (acc.kind === "any") return constraint;
|
|
122
|
+
if (acc.kind === "empty" || constraint.kind === "empty") return { kind: "empty" };
|
|
123
|
+
const ranges = [];
|
|
124
|
+
for (const a of acc.ranges) for (const b of constraint.ranges) {
|
|
125
|
+
const lo = Math.max(a.lo, b.lo);
|
|
126
|
+
const hi = Math.min(a.hi, b.hi);
|
|
127
|
+
if (lo <= hi) ranges.push({ lo, hi });
|
|
128
|
+
}
|
|
129
|
+
return ranges.length === 0 ? { kind: "empty" } : { kind: "ranges", ranges };
|
|
130
|
+
}
|
|
131
|
+
function applyAssertion(fs2, assertion) {
|
|
132
|
+
if (assertion === null) return fs2;
|
|
133
|
+
if (fs2.kind === "empty") return assertion;
|
|
134
|
+
return narrowBy(fs2, assertion) ?? fs2;
|
|
135
|
+
}
|
|
101
136
|
function sequenceFirstSet(parsers) {
|
|
102
137
|
let fs2 = empty();
|
|
138
|
+
let assertion = null;
|
|
103
139
|
for (const p of parsers) {
|
|
140
|
+
if (isPositiveLookahead(p)) {
|
|
141
|
+
assertion = narrowBy(assertion, p._meta.firstSet);
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
104
144
|
if (!isZeroWidthAssertion(p)) fs2 = union(fs2, p._meta.firstSet);
|
|
105
|
-
if (!matchesEmpty(p)) return fs2;
|
|
145
|
+
if (!matchesEmpty(p)) return applyAssertion(fs2, assertion);
|
|
106
146
|
}
|
|
107
|
-
return fs2;
|
|
147
|
+
return applyAssertion(fs2, assertion);
|
|
108
148
|
}
|
|
109
|
-
function firstSetOf(p, seen = /* @__PURE__ */ new Set()) {
|
|
149
|
+
function firstSetOf(p, seen = /* @__PURE__ */ new Set(), resolve2) {
|
|
110
150
|
if (seen.has(p)) return any();
|
|
111
151
|
seen.add(p);
|
|
112
|
-
const fs2 = (c) => firstSetOf(c, seen);
|
|
152
|
+
const fs2 = (c) => firstSetOf(c, seen, resolve2);
|
|
153
|
+
const empties = (c) => matchesEmpty(c, /* @__PURE__ */ new Set(), resolve2);
|
|
113
154
|
const d = p._def;
|
|
114
155
|
switch (d.tag) {
|
|
115
156
|
case "literal":
|
|
@@ -121,7 +162,8 @@ function firstSetOf(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
121
162
|
try {
|
|
122
163
|
return fs2(d.thunk());
|
|
123
164
|
} catch {
|
|
124
|
-
|
|
165
|
+
const t = resolveNamedRef(p, resolve2);
|
|
166
|
+
return t ? fs2(t) : any();
|
|
125
167
|
}
|
|
126
168
|
case "choice": {
|
|
127
169
|
let out = empty();
|
|
@@ -130,11 +172,20 @@ function firstSetOf(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
130
172
|
}
|
|
131
173
|
case "sequence": {
|
|
132
174
|
let out = empty();
|
|
175
|
+
let assertion = null;
|
|
133
176
|
for (const term of d.parsers) {
|
|
177
|
+
if (isPositiveLookahead(term)) {
|
|
178
|
+
assertion = narrowBy(assertion, fs2(term));
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
134
181
|
if (!isZeroWidthAssertion(term)) out = union(out, fs2(term));
|
|
135
|
-
if (!
|
|
182
|
+
if (!empties(term)) return applyAssertion(out, assertion);
|
|
136
183
|
}
|
|
137
|
-
return out;
|
|
184
|
+
return applyAssertion(out, assertion);
|
|
185
|
+
}
|
|
186
|
+
case "peek": {
|
|
187
|
+
const inner = d.parser;
|
|
188
|
+
return empties(inner) ? any() : fs2(inner);
|
|
138
189
|
}
|
|
139
190
|
case "oneOrMore":
|
|
140
191
|
case "many":
|
|
@@ -150,6 +201,7 @@ function firstSetOf(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
150
201
|
return fs2(d.parser);
|
|
151
202
|
case "sepBy":
|
|
152
203
|
return fs2(d.parser);
|
|
204
|
+
// both min 0 and min 1 start with the item
|
|
153
205
|
case "skip":
|
|
154
206
|
return fs2(d.main);
|
|
155
207
|
default:
|
|
@@ -871,9 +923,46 @@ function firstSetFromRegex(source) {
|
|
|
871
923
|
return { firstSet: firstSet(ast), canMatchNewline: firstCanBeNewline(ast) };
|
|
872
924
|
}
|
|
873
925
|
|
|
926
|
+
// src/combinators/case-fold.ts
|
|
927
|
+
function canonical(ch2) {
|
|
928
|
+
const u = ch2.toUpperCase();
|
|
929
|
+
if (u.length !== 1) return ch2;
|
|
930
|
+
if (ch2.codePointAt(0) >= 128 && u.codePointAt(0) < 128) return ch2;
|
|
931
|
+
return u;
|
|
932
|
+
}
|
|
933
|
+
var foldIndex = null;
|
|
934
|
+
function buildFoldIndex() {
|
|
935
|
+
const byCanonical = /* @__PURE__ */ new Map();
|
|
936
|
+
for (let cp = 0; cp < 65536; cp++) {
|
|
937
|
+
if (cp >= 55296 && cp <= 57343) continue;
|
|
938
|
+
const key = canonical(String.fromCharCode(cp));
|
|
939
|
+
const bucket = byCanonical.get(key);
|
|
940
|
+
if (bucket) bucket.push(cp);
|
|
941
|
+
else byCanonical.set(key, [cp]);
|
|
942
|
+
}
|
|
943
|
+
const index = /* @__PURE__ */ new Map();
|
|
944
|
+
for (const members of byCanonical.values()) {
|
|
945
|
+
if (members.length < 2) continue;
|
|
946
|
+
for (const cp of members) index.set(cp, members);
|
|
947
|
+
}
|
|
948
|
+
return index;
|
|
949
|
+
}
|
|
950
|
+
function caseFoldVariants(cp) {
|
|
951
|
+
if (cp < 128) {
|
|
952
|
+
if (cp >= 65 && cp <= 90) return [cp, cp + 32];
|
|
953
|
+
if (cp >= 97 && cp <= 122) return [cp, cp - 32];
|
|
954
|
+
return [cp];
|
|
955
|
+
}
|
|
956
|
+
if (cp > 65535) return [cp];
|
|
957
|
+
foldIndex ??= buildFoldIndex();
|
|
958
|
+
return foldIndex.get(cp) ?? [cp];
|
|
959
|
+
}
|
|
960
|
+
|
|
874
961
|
// src/combinators/keywords.ts
|
|
875
|
-
function word(str,
|
|
876
|
-
|
|
962
|
+
function word(str, boundaryOrOpts, opts) {
|
|
963
|
+
const boundary = typeof boundaryOrOpts === "string" ? boundaryOrOpts : "_0-9A-Za-z";
|
|
964
|
+
const rest = typeof boundaryOrOpts === "object" && boundaryOrOpts !== null ? boundaryOrOpts : opts;
|
|
965
|
+
return keywords([str], { ...rest, boundary });
|
|
877
966
|
}
|
|
878
967
|
function escapeRe(s) {
|
|
879
968
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -882,7 +971,7 @@ function keywords(words, opts = {}) {
|
|
|
882
971
|
const sorted = [...new Set(words)].sort((a, b) => b.length - a.length);
|
|
883
972
|
const alt = sorted.map(escapeRe).join("|");
|
|
884
973
|
const boundary = opts.boundary ? `(?![${opts.boundary}])` : "";
|
|
885
|
-
const flags = opts.caseInsensitive ? "
|
|
974
|
+
const flags = opts.caseInsensitive ? "iy" : "uy";
|
|
886
975
|
const re = new RegExp(`(?:${alt})${boundary}`, flags);
|
|
887
976
|
let firstSet2 = empty();
|
|
888
977
|
for (const w of sorted) {
|
|
@@ -890,10 +979,7 @@ function keywords(words, opts = {}) {
|
|
|
890
979
|
const cp = w.codePointAt(0);
|
|
891
980
|
firstSet2 = union(firstSet2, fromChar(cp));
|
|
892
981
|
if (opts.caseInsensitive) {
|
|
893
|
-
const
|
|
894
|
-
const l = String.fromCodePoint(cp).toLowerCase().codePointAt(0);
|
|
895
|
-
if (u !== void 0) firstSet2 = union(firstSet2, fromChar(u));
|
|
896
|
-
if (l !== void 0) firstSet2 = union(firstSet2, fromChar(l));
|
|
982
|
+
for (const v2 of caseFoldVariants(cp)) firstSet2 = union(firstSet2, fromChar(v2));
|
|
897
983
|
}
|
|
898
984
|
}
|
|
899
985
|
const meta = { firstSet: firstSet2, canMatchNewline: false, isTrivia: false };
|
|
@@ -1041,6 +1127,7 @@ function deriveExpected(c) {
|
|
|
1041
1127
|
case "oneOrMore":
|
|
1042
1128
|
case "transform":
|
|
1043
1129
|
case "not":
|
|
1130
|
+
case "peek":
|
|
1044
1131
|
return deriveExpected(def.parser);
|
|
1045
1132
|
case "lazy": {
|
|
1046
1133
|
const name = c._ruleName;
|
|
@@ -1511,6 +1598,13 @@ function rollbackTrivia(ctx, mark) {
|
|
|
1511
1598
|
rollbackCstCapture(ctx, { raw: mark.raw, tlog: mark.tlog, leaves: mark.leaves, fields: mark.fields, errors: mark.errors });
|
|
1512
1599
|
if (ctx._triviaLog) ctx._triviaLog.length = mark.log;
|
|
1513
1600
|
}
|
|
1601
|
+
function saveLookaheadMark(ctx) {
|
|
1602
|
+
return { ...saveTriviaMark(ctx), probeBest: ctx._probe ? ctx._probe.best : null };
|
|
1603
|
+
}
|
|
1604
|
+
function rollbackLookahead(ctx, mark) {
|
|
1605
|
+
rollbackTrivia(ctx, mark);
|
|
1606
|
+
if (ctx._probe) ctx._probe.best = mark.probeBest;
|
|
1607
|
+
}
|
|
1514
1608
|
function scanWithLabels(input, cur, ctx) {
|
|
1515
1609
|
const triviaP = ctx.trivia;
|
|
1516
1610
|
const spec = analyzeLabeledTrivia(triviaP);
|
|
@@ -1908,13 +2002,26 @@ function repItem(combinator, input, cur, ctx, guardable) {
|
|
|
1908
2002
|
}
|
|
1909
2003
|
return { value: result.value, end: result.span.end };
|
|
1910
2004
|
}
|
|
1911
|
-
function
|
|
2005
|
+
function resolveBounds(what, opts) {
|
|
2006
|
+
const min = opts.min ?? 0;
|
|
2007
|
+
const max = opts.max ?? Infinity;
|
|
2008
|
+
const bad = (msg) => {
|
|
2009
|
+
throw new RangeError(`parseman: ${what} ${msg}`);
|
|
2010
|
+
};
|
|
2011
|
+
if (!Number.isInteger(min) || min < 0) bad(`min must be a non-negative integer (got ${String(opts.min)})`);
|
|
2012
|
+
if (max !== Infinity && (!Number.isInteger(max) || max < 1)) bad(`max must be a positive integer (got ${String(opts.max)})`);
|
|
2013
|
+
if (max < min) bad(`max (${max}) is less than min (${min}) \u2014 the combinator could never succeed`);
|
|
2014
|
+
return { min, max };
|
|
2015
|
+
}
|
|
2016
|
+
function many(combinator, opts = {}) {
|
|
2017
|
+
const { min, max } = resolveBounds("many()", opts);
|
|
2018
|
+
if (min >= 1) return atLeast(combinator, min, max);
|
|
1912
2019
|
const meta = {
|
|
1913
2020
|
firstSet: combinator._meta.firstSet,
|
|
1914
2021
|
canMatchNewline: combinator._meta.canMatchNewline,
|
|
1915
2022
|
isTrivia: false
|
|
1916
2023
|
};
|
|
1917
|
-
const def = { tag: "many", parser: combinator, min: 0 };
|
|
2024
|
+
const def = { tag: "many", parser: combinator, min: 0, ...max === Infinity ? {} : { max } };
|
|
1918
2025
|
let expected;
|
|
1919
2026
|
const guardable = combinator._meta.firstSet.kind !== "any" && !matchesEmpty(combinator);
|
|
1920
2027
|
return {
|
|
@@ -1924,7 +2031,9 @@ function many(combinator) {
|
|
|
1924
2031
|
parse(input, pos, ctx) {
|
|
1925
2032
|
const values = def.valueUnused ? void 0 : [];
|
|
1926
2033
|
let cur = pos;
|
|
2034
|
+
let count = 0;
|
|
1927
2035
|
while (cur < input.length) {
|
|
2036
|
+
if (count >= max) break;
|
|
1928
2037
|
const item = repItem(combinator, input, cur, ctx, guardable);
|
|
1929
2038
|
if (item === "stop") break;
|
|
1930
2039
|
if ("fail" in item) {
|
|
@@ -1935,23 +2044,28 @@ function many(combinator) {
|
|
|
1935
2044
|
const { error, end } = recoverScan(input, item.failPos, ctx, sync, expected);
|
|
1936
2045
|
if (values !== void 0) values.push(error);
|
|
1937
2046
|
captureError(ctx, error);
|
|
2047
|
+
count++;
|
|
1938
2048
|
cur = end;
|
|
1939
2049
|
continue;
|
|
1940
2050
|
}
|
|
1941
2051
|
if (values !== void 0) values.push(item.value);
|
|
2052
|
+
count++;
|
|
1942
2053
|
cur = item.end;
|
|
1943
2054
|
}
|
|
1944
2055
|
return { ok: true, value: values ?? void 0, span: { start: pos, end: cur } };
|
|
1945
2056
|
}
|
|
1946
2057
|
};
|
|
1947
2058
|
}
|
|
1948
|
-
function oneOrMore(combinator) {
|
|
2059
|
+
function oneOrMore(combinator, opts = {}) {
|
|
2060
|
+
return many(combinator, { ...opts, min: opts.min ?? 1 });
|
|
2061
|
+
}
|
|
2062
|
+
function atLeast(combinator, min, max) {
|
|
1949
2063
|
const meta = {
|
|
1950
2064
|
firstSet: combinator._meta.firstSet,
|
|
1951
2065
|
canMatchNewline: combinator._meta.canMatchNewline,
|
|
1952
2066
|
isTrivia: false
|
|
1953
2067
|
};
|
|
1954
|
-
const def = { tag: "oneOrMore", parser: combinator, min:
|
|
2068
|
+
const def = { tag: "oneOrMore", parser: combinator, min, ...max === Infinity ? {} : { max } };
|
|
1955
2069
|
let expected;
|
|
1956
2070
|
const guardable = combinator._meta.firstSet.kind !== "any" && !matchesEmpty(combinator);
|
|
1957
2071
|
return {
|
|
@@ -1963,7 +2077,19 @@ function oneOrMore(combinator) {
|
|
|
1963
2077
|
if (!first.ok) return first;
|
|
1964
2078
|
const values = def.valueUnused ? void 0 : [first.value];
|
|
1965
2079
|
let cur = first.span.end;
|
|
2080
|
+
let count = 1;
|
|
2081
|
+
while (count < min) {
|
|
2082
|
+
const item = repItem(combinator, input, cur, ctx, guardable);
|
|
2083
|
+
if (item === "stop" || "fail" in item) {
|
|
2084
|
+
expected ??= deriveExpected(combinator);
|
|
2085
|
+
return { ok: false, expected: expected.length > 0 ? expected : [combinator._tag], span: { start: cur, end: cur } };
|
|
2086
|
+
}
|
|
2087
|
+
if (values !== void 0) values.push(item.value);
|
|
2088
|
+
cur = item.end;
|
|
2089
|
+
count++;
|
|
2090
|
+
}
|
|
1966
2091
|
while (cur < input.length) {
|
|
2092
|
+
if (count >= max) break;
|
|
1967
2093
|
const item = repItem(combinator, input, cur, ctx, guardable);
|
|
1968
2094
|
if (item === "stop") break;
|
|
1969
2095
|
if ("fail" in item) {
|
|
@@ -1974,10 +2100,12 @@ function oneOrMore(combinator) {
|
|
|
1974
2100
|
const { error, end } = recoverScan(input, item.failPos, ctx, sync, expected);
|
|
1975
2101
|
if (values !== void 0) values.push(error);
|
|
1976
2102
|
captureError(ctx, error);
|
|
2103
|
+
count++;
|
|
1977
2104
|
cur = end;
|
|
1978
2105
|
continue;
|
|
1979
2106
|
}
|
|
1980
2107
|
if (values !== void 0) values.push(item.value);
|
|
2108
|
+
count++;
|
|
1981
2109
|
cur = item.end;
|
|
1982
2110
|
}
|
|
1983
2111
|
return { ok: true, value: values ?? void 0, span: { start: pos, end: cur } };
|
|
@@ -2007,17 +2135,33 @@ function optional(combinator) {
|
|
|
2007
2135
|
}
|
|
2008
2136
|
};
|
|
2009
2137
|
}
|
|
2010
|
-
function
|
|
2138
|
+
function oneOrMoreSep(combinator, separator, opts = {}) {
|
|
2139
|
+
return sepBy(combinator, separator, { ...opts, min: opts.min ?? 1 });
|
|
2140
|
+
}
|
|
2141
|
+
function sepBy(combinator, separator, opts = {}) {
|
|
2142
|
+
const { min, max } = resolveBounds("sepBy()", opts);
|
|
2143
|
+
const trailing = opts.trailing ?? "forbid";
|
|
2011
2144
|
const meta = {
|
|
2012
2145
|
firstSet: combinator._meta.firstSet,
|
|
2013
2146
|
canMatchNewline: combinator._meta.canMatchNewline || separator._meta.canMatchNewline,
|
|
2014
2147
|
isTrivia: false
|
|
2015
2148
|
};
|
|
2016
2149
|
let expected;
|
|
2150
|
+
const failAt2 = (at) => {
|
|
2151
|
+
expected ??= deriveExpected(combinator);
|
|
2152
|
+
return { ok: false, expected: expected.length > 0 ? expected : [combinator._tag], span: { start: at, end: at } };
|
|
2153
|
+
};
|
|
2017
2154
|
return {
|
|
2018
2155
|
_tag: "sepBy",
|
|
2019
2156
|
_meta: meta,
|
|
2020
|
-
_def: {
|
|
2157
|
+
_def: {
|
|
2158
|
+
tag: "sepBy",
|
|
2159
|
+
parser: combinator,
|
|
2160
|
+
separator,
|
|
2161
|
+
min,
|
|
2162
|
+
...max === Infinity ? {} : { max },
|
|
2163
|
+
...trailing === "forbid" ? {} : { trailing }
|
|
2164
|
+
},
|
|
2021
2165
|
parse(input, pos, ctx) {
|
|
2022
2166
|
const first = combinator.parse(input, pos, ctx);
|
|
2023
2167
|
const values = [];
|
|
@@ -2028,6 +2172,7 @@ function sepBy(combinator, separator) {
|
|
|
2028
2172
|
} else {
|
|
2029
2173
|
const term = ctx._tolerant ? ctx._sync : void 0;
|
|
2030
2174
|
if (term === void 0 || matchesAt(term, input, pos, ctx)) {
|
|
2175
|
+
if (min >= 1) return failAt2(pos);
|
|
2031
2176
|
return { ok: true, value: [], span: { start: pos, end: pos } };
|
|
2032
2177
|
}
|
|
2033
2178
|
expected ??= deriveExpected(combinator);
|
|
@@ -2037,6 +2182,7 @@ function sepBy(combinator, separator) {
|
|
|
2037
2182
|
cur = rec.end;
|
|
2038
2183
|
}
|
|
2039
2184
|
while (cur < input.length) {
|
|
2185
|
+
if (values.length >= max) break;
|
|
2040
2186
|
const loopMark = saveTriviaMark(ctx);
|
|
2041
2187
|
let sepPos = cur;
|
|
2042
2188
|
if (ctx.trivia) {
|
|
@@ -2053,6 +2199,7 @@ function sepBy(combinator, separator) {
|
|
|
2053
2199
|
rollbackTrivia(ctx, loopMark);
|
|
2054
2200
|
break;
|
|
2055
2201
|
}
|
|
2202
|
+
const sepMark = trailing === "forbid" ? void 0 : saveTriviaMark(ctx);
|
|
2056
2203
|
let nextPos = sep2.span.end;
|
|
2057
2204
|
if (ctx.trivia) {
|
|
2058
2205
|
if (needsDeferredTriviaCommit(ctx)) {
|
|
@@ -2074,12 +2221,18 @@ function sepBy(combinator, separator) {
|
|
|
2074
2221
|
cur = rec.end;
|
|
2075
2222
|
continue;
|
|
2076
2223
|
}
|
|
2224
|
+
if (sepMark !== void 0) {
|
|
2225
|
+
rollbackTrivia(ctx, sepMark);
|
|
2226
|
+
cur = sep2.span.end;
|
|
2227
|
+
break;
|
|
2228
|
+
}
|
|
2077
2229
|
rollbackTrivia(ctx, loopMark);
|
|
2078
2230
|
break;
|
|
2079
2231
|
}
|
|
2080
2232
|
values.push(next.value);
|
|
2081
2233
|
cur = next.span.end;
|
|
2082
2234
|
}
|
|
2235
|
+
if (values.length < min) return failAt2(cur);
|
|
2083
2236
|
return { ok: true, value: values, span: { start: pos, end: cur } };
|
|
2084
2237
|
}
|
|
2085
2238
|
};
|
|
@@ -2132,6 +2285,7 @@ function markUnusedValues(root) {
|
|
|
2132
2285
|
visit(def.separator, false);
|
|
2133
2286
|
return;
|
|
2134
2287
|
case "not":
|
|
2288
|
+
case "peek":
|
|
2135
2289
|
visit(def.parser, false);
|
|
2136
2290
|
return;
|
|
2137
2291
|
case "grammar":
|
|
@@ -2250,9 +2404,9 @@ function not(combinator) {
|
|
|
2250
2404
|
_meta: meta,
|
|
2251
2405
|
_def: { tag: "not", parser: combinator },
|
|
2252
2406
|
parse(input, pos, ctx) {
|
|
2253
|
-
const mark =
|
|
2407
|
+
const mark = saveLookaheadMark(ctx);
|
|
2254
2408
|
const result = combinator.parse(input, pos, ctx);
|
|
2255
|
-
|
|
2409
|
+
rollbackLookahead(ctx, mark);
|
|
2256
2410
|
if (result.ok) {
|
|
2257
2411
|
return { ok: false, expected: [`not(${combinator._tag})`], span: { start: pos, end: pos } };
|
|
2258
2412
|
}
|
|
@@ -2261,6 +2415,28 @@ function not(combinator) {
|
|
|
2261
2415
|
};
|
|
2262
2416
|
}
|
|
2263
2417
|
|
|
2418
|
+
// src/combinators/peek.ts
|
|
2419
|
+
function peek(combinator) {
|
|
2420
|
+
const meta = {
|
|
2421
|
+
// A nullable body imposes no first-char constraint — `any()`, not its set.
|
|
2422
|
+
firstSet: matchesEmpty(combinator) ? any() : combinator._meta.firstSet,
|
|
2423
|
+
canMatchNewline: false,
|
|
2424
|
+
isTrivia: false
|
|
2425
|
+
};
|
|
2426
|
+
return {
|
|
2427
|
+
_tag: "peek",
|
|
2428
|
+
_meta: meta,
|
|
2429
|
+
_def: { tag: "peek", parser: combinator },
|
|
2430
|
+
parse(input, pos, ctx) {
|
|
2431
|
+
const mark = saveLookaheadMark(ctx);
|
|
2432
|
+
const result = combinator.parse(input, pos, ctx);
|
|
2433
|
+
rollbackLookahead(ctx, mark);
|
|
2434
|
+
if (result.ok) return { ok: true, value: null, span: { start: pos, end: pos } };
|
|
2435
|
+
return { ok: false, expected: [`peek(${combinator._tag})`], span: { start: pos, end: pos } };
|
|
2436
|
+
}
|
|
2437
|
+
};
|
|
2438
|
+
}
|
|
2439
|
+
|
|
2264
2440
|
// src/compiler/build-arity.ts
|
|
2265
2441
|
var PARAM_LIST_RE = (
|
|
2266
2442
|
// (params) => ... | function name?(params) ... | single-ident arrow `x => ...`
|
|
@@ -2359,6 +2535,7 @@ function parserHasOwnFields(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
2359
2535
|
case "expect":
|
|
2360
2536
|
case "withCtx":
|
|
2361
2537
|
case "not":
|
|
2538
|
+
case "peek":
|
|
2362
2539
|
return parserHasOwnFields(d.parser, seen);
|
|
2363
2540
|
default:
|
|
2364
2541
|
return false;
|
|
@@ -2398,6 +2575,7 @@ function parserHasTriviaSite(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
2398
2575
|
case "expect":
|
|
2399
2576
|
case "withCtx":
|
|
2400
2577
|
case "not":
|
|
2578
|
+
case "peek":
|
|
2401
2579
|
case "field":
|
|
2402
2580
|
return parserHasTriviaSite(d.parser, seen);
|
|
2403
2581
|
case "choice":
|
|
@@ -2447,6 +2625,7 @@ function parserEnablesTriviaCapture(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
2447
2625
|
case "expect":
|
|
2448
2626
|
case "withCtx":
|
|
2449
2627
|
case "not":
|
|
2628
|
+
case "peek":
|
|
2450
2629
|
return parserEnablesTriviaCapture(d.parser, seen);
|
|
2451
2630
|
// leaf() clears the inner CST/trivia collector and publishes only its own
|
|
2452
2631
|
// terminal result. An inner parser() capture cannot reach this node.
|
|
@@ -2795,7 +2974,7 @@ function leaf(root, fn) {
|
|
|
2795
2974
|
}
|
|
2796
2975
|
|
|
2797
2976
|
// src/version.ts
|
|
2798
|
-
var PARSEMAN_VERSION = "0.
|
|
2977
|
+
var PARSEMAN_VERSION = "0.34.0";
|
|
2799
2978
|
|
|
2800
2979
|
// src/analysis/gating.ts
|
|
2801
2980
|
var isAny = (fs2) => fs2.kind === "any";
|
|
@@ -2835,14 +3014,15 @@ function firstSetToString(fs2) {
|
|
|
2835
3014
|
var ruleNameOf = (p) => p._ruleName;
|
|
2836
3015
|
var SUGGESTIONS = {
|
|
2837
3016
|
"broad-recognizer": "if this arm leads with a keyword regex, use word('kw', boundary) / keywords([...]) \u2014 they expose an EXACT, gating first-set and lower to the same charCodeAt scan. A genuine scanTo fallback is fine; accept it in the gating snapshot if intentional.",
|
|
2838
|
-
"leading-not": "let the arm lead with its actual consuming terminal (first-sets gate it automatically); keep not(...) only as a TRAILING boundary. Do not hand-roll not(not(...)) to fake gating.",
|
|
2839
|
-
"nullable-prefix": "a leading optional/many lets a later, broad term start the arm. Split the empty case into its own arm, or gate on the prefix first char.",
|
|
3017
|
+
"leading-not": "let the arm lead with its actual consuming terminal (first-sets gate it automatically); keep not(...) only as a TRAILING boundary. Do not hand-roll not(not(...)) to fake gating \u2014 use peek(X), which is zero-width AND carries X first-set.",
|
|
3018
|
+
"nullable-prefix": "a leading optional/many lets a later, broad term start the arm. Split the empty case into its own arm, or gate on the prefix first char. A plain sepBy(item, sep) is ALSO nullable \u2014 pass { min: 1 } for a list that cannot be empty.",
|
|
2840
3019
|
"cross-artifact-ref": "parseman >=0.32.0 resolves a g.Foo ref first-set at fuse time; if still ANY the target rule is itself ungated \u2014 analyze it and give it a concrete non-nullable lead.",
|
|
2841
3020
|
"opaque-wrapper": "this wrapper (guard/withCtx/recover) contributes no first char; put a concrete leading terminal before it.",
|
|
2842
3021
|
"ref-cycle": "a recursive ref resolved to ANY; ensure the recursion has a concrete terminal lead on the base case."
|
|
2843
3022
|
};
|
|
2844
|
-
function classifyBroadArm(arm) {
|
|
3023
|
+
function classifyBroadArm(arm, resolve2) {
|
|
2845
3024
|
const seen = /* @__PURE__ */ new Set();
|
|
3025
|
+
const relabel = (inner, cause, detail) => ({ cause, detail, ...inner?.unresolvedExternal ? { unresolvedExternal: true } : {} });
|
|
2846
3026
|
const walk2 = (p) => {
|
|
2847
3027
|
if (seen.has(p)) return { cause: "ref-cycle", detail: "ref cycle" };
|
|
2848
3028
|
seen.add(p);
|
|
@@ -2854,6 +3034,10 @@ function classifyBroadArm(arm) {
|
|
|
2854
3034
|
return isAny(p._meta.firstSet) ? { cause: "broad-recognizer", detail: `broad recognizer (${d.tag})` } : null;
|
|
2855
3035
|
case "not":
|
|
2856
3036
|
return { cause: "leading-not", detail: "leading not(...) (first-set ANY)" };
|
|
3037
|
+
// peek() CARRIES its body's first-set, so it is only broad when the body is
|
|
3038
|
+
// (or the body is nullable) — walk in for the real cause.
|
|
3039
|
+
case "peek":
|
|
3040
|
+
return walk2(d.parser);
|
|
2857
3041
|
case "scanTo":
|
|
2858
3042
|
return { cause: "broad-recognizer", detail: "scanTo (any first char)" };
|
|
2859
3043
|
// `guard` (the `gate()` state predicate) has a genuinely-`any` first-set of
|
|
@@ -2870,18 +3054,23 @@ function classifyBroadArm(arm) {
|
|
|
2870
3054
|
return walk2(d.parser);
|
|
2871
3055
|
case "lazy": {
|
|
2872
3056
|
const name = p._ruleName;
|
|
3057
|
+
let target;
|
|
2873
3058
|
try {
|
|
2874
|
-
|
|
2875
|
-
if (name !== void 0) return inner ? { cause: "cross-artifact-ref", detail: `via ref g.${name} \u2192 ${inner.detail}` } : null;
|
|
2876
|
-
return inner;
|
|
3059
|
+
target = d.thunk();
|
|
2877
3060
|
} catch {
|
|
2878
|
-
|
|
3061
|
+
const bound = name !== void 0 ? resolve2?.(name) : void 0;
|
|
3062
|
+
if (bound === void 0)
|
|
3063
|
+
return { cause: "cross-artifact-ref", detail: `unresolved ref${name ? ` g.${name}` : ""}`, unresolvedExternal: name !== void 0 };
|
|
3064
|
+
target = bound;
|
|
2879
3065
|
}
|
|
3066
|
+
const inner = walk2(target);
|
|
3067
|
+
if (name !== void 0) return inner ? relabel(inner, "cross-artifact-ref", `via ref g.${name} \u2192 ${inner.detail}`) : null;
|
|
3068
|
+
return inner;
|
|
2880
3069
|
}
|
|
2881
3070
|
case "optional":
|
|
2882
3071
|
case "many": {
|
|
2883
3072
|
const inner = walk2(d.parser);
|
|
2884
|
-
return
|
|
3073
|
+
return relabel(inner, "nullable-prefix", inner ? `nullable prefix \u2192 ${inner.detail}` : "nullable prefix to broad term");
|
|
2885
3074
|
}
|
|
2886
3075
|
case "oneOrMore":
|
|
2887
3076
|
case "transform":
|
|
@@ -2903,13 +3092,13 @@ function classifyBroadArm(arm) {
|
|
|
2903
3092
|
sawNullablePrefix = true;
|
|
2904
3093
|
continue;
|
|
2905
3094
|
}
|
|
2906
|
-
if (isAny(firstSetOf(t))) {
|
|
3095
|
+
if (isAny(firstSetOf(t, /* @__PURE__ */ new Set(), resolve2))) {
|
|
2907
3096
|
const inner = walk2(t);
|
|
2908
3097
|
if (sawNullablePrefix)
|
|
2909
|
-
return
|
|
3098
|
+
return relabel(inner, "nullable-prefix", inner ? `nullable prefix \u2192 ${inner.detail}` : "nullable prefix to broad term");
|
|
2910
3099
|
return inner ?? { cause: "broad-recognizer", detail: "sequence leading term is broad" };
|
|
2911
3100
|
}
|
|
2912
|
-
if (matchesEmpty(t)) {
|
|
3101
|
+
if (matchesEmpty(t, /* @__PURE__ */ new Set(), resolve2)) {
|
|
2913
3102
|
sawNullablePrefix = true;
|
|
2914
3103
|
continue;
|
|
2915
3104
|
}
|
|
@@ -2920,7 +3109,7 @@ function classifyBroadArm(arm) {
|
|
|
2920
3109
|
case "choice": {
|
|
2921
3110
|
for (const a of d.parsers) {
|
|
2922
3111
|
const r = walk2(a);
|
|
2923
|
-
if (r) return
|
|
3112
|
+
if (r) return relabel(r, r.cause, `choice arm \u2192 ${r.detail}`);
|
|
2924
3113
|
}
|
|
2925
3114
|
return null;
|
|
2926
3115
|
}
|
|
@@ -2957,7 +3146,7 @@ function detectAntiPatterns(rule, arms) {
|
|
|
2957
3146
|
kind: "double-not",
|
|
2958
3147
|
rule,
|
|
2959
3148
|
armIndex: i,
|
|
2960
|
-
message: "not(not(...)) hand-rolls automatic first-char gating and MISCOMPILES among shared-first-char sibling arms (its first-set is ANY, poisoning dispatch).
|
|
3149
|
+
message: "not(not(...)) hand-rolls automatic first-char gating and MISCOMPILES among shared-first-char sibling arms (its first-set is ANY, poisoning dispatch). Prefer letting the arm lead with its consuming terminal; where a real positive lookahead IS needed, use peek(X) \u2014 zero-width like not(not(X)) but it CARRIES X's first-set, so the arm still dispatches."
|
|
2961
3150
|
});
|
|
2962
3151
|
} else {
|
|
2963
3152
|
out.push({
|
|
@@ -2980,6 +3169,9 @@ function detectAntiPatterns(rule, arms) {
|
|
|
2980
3169
|
return out;
|
|
2981
3170
|
}
|
|
2982
3171
|
function analyzeGating(entry, opts) {
|
|
3172
|
+
return analyzeGatingRules([[ruleNameOf(entry) ?? opts?.entryName ?? "<entry>", entry]], opts);
|
|
3173
|
+
}
|
|
3174
|
+
function analyzeGatingRules(ruleMap, opts) {
|
|
2983
3175
|
const raw = [];
|
|
2984
3176
|
const antiPatterns = [];
|
|
2985
3177
|
const visited = /* @__PURE__ */ new Set();
|
|
@@ -2989,7 +3181,7 @@ function analyzeGating(entry, opts) {
|
|
|
2989
3181
|
const d = p._def;
|
|
2990
3182
|
const rule = ruleNameOf(p) ?? enclosingRule;
|
|
2991
3183
|
if (d.tag === "choice") {
|
|
2992
|
-
raw.push({ g: analyzeChoice(p, d, rule), rule });
|
|
3184
|
+
raw.push({ g: analyzeChoice(p, d, rule, opts?.resolveRef), rule });
|
|
2993
3185
|
antiPatterns.push(...detectAntiPatterns(rule, d.parsers));
|
|
2994
3186
|
}
|
|
2995
3187
|
const rec = d;
|
|
@@ -3006,7 +3198,7 @@ function analyzeGating(entry, opts) {
|
|
|
3006
3198
|
}
|
|
3007
3199
|
for (const k of kids) visit(k, rule);
|
|
3008
3200
|
};
|
|
3009
|
-
visit(
|
|
3201
|
+
for (const [name, root] of ruleMap) visit(root, ruleNameOf(root) ?? name);
|
|
3010
3202
|
const perRule = /* @__PURE__ */ new Map();
|
|
3011
3203
|
for (const r of raw) perRule.set(r.rule, (perRule.get(r.rule) ?? 0) + 1);
|
|
3012
3204
|
const seenInRule = /* @__PURE__ */ new Map();
|
|
@@ -3022,18 +3214,19 @@ function analyzeGating(entry, opts) {
|
|
|
3022
3214
|
});
|
|
3023
3215
|
const gated = choices.filter((c) => c.gates === "yes").length;
|
|
3024
3216
|
const recoverable = choices.filter((c) => c.gates === "recoverable").length;
|
|
3025
|
-
const ungated = choices.filter((c) => c.gates === "no" && !c.accepted);
|
|
3217
|
+
const ungated = choices.filter((c) => c.gates === "no" && !c.accepted && !c.deferred);
|
|
3026
3218
|
const accepted = choices.filter((c) => c.gates === "no" && c.accepted);
|
|
3219
|
+
const deferred = choices.filter((c) => c.gates === "no" && !c.accepted && c.deferred);
|
|
3027
3220
|
const acceptedUnused = [...accept].filter((id) => !usedAccept.has(id));
|
|
3028
|
-
return { totalChoices: choices.length, gated, recoverable, ungated, accepted, acceptedUnused, choices, antiPatterns };
|
|
3221
|
+
return { totalChoices: choices.length, gated, recoverable, ungated, accepted, deferred, acceptedUnused, choices, antiPatterns };
|
|
3029
3222
|
}
|
|
3030
|
-
function analyzeChoice(p, d, rule) {
|
|
3223
|
+
function analyzeChoice(p, d, rule, resolve2) {
|
|
3031
3224
|
const arms = d.parsers;
|
|
3032
3225
|
const shallow = arms.map((a) => a._meta.firstSet);
|
|
3033
|
-
const deep = arms.map((a) => firstSetOf(a));
|
|
3226
|
+
const deep = arms.map((a) => firstSetOf(a, /* @__PURE__ */ new Set(), resolve2));
|
|
3034
3227
|
const anyArms = deep.map((fs2, index) => ({ fs: fs2, index })).filter((x) => isAny(x.fs)).map(({ index }) => {
|
|
3035
|
-
const { cause, detail } = classifyBroadArm(arms[index]);
|
|
3036
|
-
return { index, cause, detail, shallowAnyOnly: false, suggestion: SUGGESTIONS[cause] };
|
|
3228
|
+
const { cause, detail, unresolvedExternal } = classifyBroadArm(arms[index], resolve2);
|
|
3229
|
+
return { index, cause, detail, shallowAnyOnly: false, unresolvedExternal: unresolvedExternal === true, suggestion: SUGGESTIONS[cause] };
|
|
3037
3230
|
});
|
|
3038
3231
|
const overlaps = [];
|
|
3039
3232
|
for (let i = 0; i < deep.length; i++)
|
|
@@ -3049,10 +3242,12 @@ function analyzeChoice(p, d, rule) {
|
|
|
3049
3242
|
if (d.disjoint) gates = "yes";
|
|
3050
3243
|
else if (anyArms.length === 0 && overlaps.length === 0) gates = "recoverable";
|
|
3051
3244
|
else gates = "no";
|
|
3245
|
+
const deferred = gates === "no" && overlaps.length === 0 && anyArms.length > 0 && anyArms.every((a) => a.unresolvedExternal);
|
|
3052
3246
|
return {
|
|
3053
3247
|
rule,
|
|
3054
3248
|
strategy: d.strategy?.tag ?? "firstMatch",
|
|
3055
3249
|
gates,
|
|
3250
|
+
deferred,
|
|
3056
3251
|
combinedFirstSet: { shallow: combine(shallow), deep: combine(deep) },
|
|
3057
3252
|
anyArms,
|
|
3058
3253
|
overlaps
|
|
@@ -3219,6 +3414,7 @@ function children(def, winners) {
|
|
|
3219
3414
|
case "field":
|
|
3220
3415
|
case "grammar":
|
|
3221
3416
|
case "not":
|
|
3417
|
+
case "peek":
|
|
3222
3418
|
case "node":
|
|
3223
3419
|
case "guard":
|
|
3224
3420
|
case "withCtx":
|
|
@@ -4992,6 +5188,9 @@ function mayLeavePartialCapture(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
4992
5188
|
case "keywords":
|
|
4993
5189
|
case "guard":
|
|
4994
5190
|
case "not":
|
|
5191
|
+
// peek(): emitted under a non-capturing probe ctx and zero-width on both
|
|
5192
|
+
// outcomes, so it can never leave a partial capture behind.
|
|
5193
|
+
case "peek":
|
|
4995
5194
|
case "trivia":
|
|
4996
5195
|
case "token":
|
|
4997
5196
|
case "leaf":
|
|
@@ -5063,6 +5262,7 @@ function capturesLeaf(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
5063
5262
|
case "leaf":
|
|
5064
5263
|
return true;
|
|
5065
5264
|
case "not":
|
|
5265
|
+
case "peek":
|
|
5066
5266
|
case "guard":
|
|
5067
5267
|
case "trivia":
|
|
5068
5268
|
case "unknown":
|
|
@@ -5189,6 +5389,7 @@ function emitKeywordsFast(def, ctx, pos) {
|
|
|
5189
5389
|
if (def.words.length === 0 || def.words.some((w) => w.length === 0)) return null;
|
|
5190
5390
|
if (def.words.some((w) => Array.from(w).length !== w.length)) return null;
|
|
5191
5391
|
if (def.caseInsensitive && def.boundary) return null;
|
|
5392
|
+
if (def.caseInsensitive && def.words.some((w) => w.split("").some((c) => c.charCodeAt(0) > 127))) return null;
|
|
5192
5393
|
let boundary = null;
|
|
5193
5394
|
if (def.boundary) {
|
|
5194
5395
|
let body = def.boundary;
|
|
@@ -5236,7 +5437,7 @@ function emitKeywords(def, ctx, pos) {
|
|
|
5236
5437
|
if (fast) return fast;
|
|
5237
5438
|
const alt = def.words.map(escapeKeywordRe).join("|");
|
|
5238
5439
|
const boundary = def.boundary ? `(?![${def.boundary}])` : "";
|
|
5239
|
-
const flags = def.caseInsensitive ? "
|
|
5440
|
+
const flags = def.caseInsensitive ? "iy" : "uy";
|
|
5240
5441
|
const source = `(?:${alt})${boundary}`;
|
|
5241
5442
|
const key = `${source}/${flags}`;
|
|
5242
5443
|
let rName = ctx.regexMap.get(key);
|
|
@@ -5382,6 +5583,7 @@ function failsAtStart(p) {
|
|
|
5382
5583
|
case "keywords":
|
|
5383
5584
|
case "guard":
|
|
5384
5585
|
case "not":
|
|
5586
|
+
case "peek":
|
|
5385
5587
|
return true;
|
|
5386
5588
|
case "transform":
|
|
5387
5589
|
case "label":
|
|
@@ -5795,14 +5997,17 @@ function emitMany(def, ctx, pos) {
|
|
|
5795
5997
|
];
|
|
5796
5998
|
const mySyncV = ctx.recovery ? v(ctx, "_mysy") : "";
|
|
5797
5999
|
if (ctx.recovery) stmts.push(`${ind(ctx)}const ${mySyncV} = _ctx._sync`);
|
|
5798
|
-
|
|
6000
|
+
for (let i = 0; i < def.min; i++) {
|
|
5799
6001
|
const firstR = emit(def.parser, ctx, curV);
|
|
5800
6002
|
stmts.push(...firstR.stmts);
|
|
5801
6003
|
if (wantValue) stmts.push(`${ind(ctx)}${arrV}.push(${firstR.valueVar})`);
|
|
5802
6004
|
stmts.push(`${ind(ctx)}${curV} = ${firstR.endVar}`);
|
|
5803
6005
|
}
|
|
6006
|
+
const maxV = def.max === void 0 ? null : wantValue ? `${arrV}.length` : v(ctx, "_cnt");
|
|
6007
|
+
if (maxV !== null && !wantValue) stmts.push(`${ind(ctx)}let ${maxV} = ${def.min}`);
|
|
5804
6008
|
stmts.push(`${ind(ctx)}while (${curV} < input.length) {`);
|
|
5805
6009
|
ctx.indent++;
|
|
6010
|
+
if (maxV !== null) stmts.push(`${ind(ctx)}if (${maxV} >= ${def.max}) break`);
|
|
5806
6011
|
let itemPos = curV;
|
|
5807
6012
|
let rollback = "";
|
|
5808
6013
|
if (ctx.activeTrivia) {
|
|
@@ -5843,7 +6048,7 @@ function emitMany(def, ctx, pos) {
|
|
|
5843
6048
|
`${ind(ctx)}if (!${iterOk}) {`,
|
|
5844
6049
|
`${ind(ctx)} ${rollback}if (_ctx._tolerant && ${mySyncV} !== undefined && !_ctx._rec.at(${mySyncV}, input, ${itemPos}, _ctx)) {`,
|
|
5845
6050
|
`${ind(ctx)} const ${rrV} = _ctx._rec.scan(input, ${itemPos}, _ctx, ${mySyncV}, ${exp})`,
|
|
5846
|
-
...wantValue ? [`${ind(ctx)} ${arrV}.push(${rrV}.error)`] : [],
|
|
6051
|
+
...wantValue ? [`${ind(ctx)} ${arrV}.push(${rrV}.error)`] : maxV !== null ? [`${ind(ctx)} ${maxV}++`] : [],
|
|
5847
6052
|
`${ind(ctx)} _ctx._rec.capture(_ctx, ${rrV}.error)`,
|
|
5848
6053
|
`${ind(ctx)} ${curV} = ${rrV}.end`,
|
|
5849
6054
|
`${ind(ctx)} continue`,
|
|
@@ -5856,6 +6061,7 @@ function emitMany(def, ctx, pos) {
|
|
|
5856
6061
|
stmts.push(`${ind(ctx)}if (!${iterOk} || ${iterEnd} <= ${itemPos}) { ${rollback}break }`);
|
|
5857
6062
|
}
|
|
5858
6063
|
if (wantValue) stmts.push(`${ind(ctx)}${arrV}.push(${iterVal})`);
|
|
6064
|
+
else if (maxV !== null) stmts.push(`${ind(ctx)}${maxV}++`);
|
|
5859
6065
|
stmts.push(`${ind(ctx)}${curV} = ${iterEnd}`);
|
|
5860
6066
|
ctx.indent--;
|
|
5861
6067
|
stmts.push(`${ind(ctx)}}`);
|
|
@@ -5920,8 +6126,8 @@ function emitSepBy(_p, def, ctx, pos) {
|
|
|
5920
6126
|
...rec ? [`${ind(ctx)}const ${mySyncV} = _ctx._sync`] : [],
|
|
5921
6127
|
...firstStmts
|
|
5922
6128
|
];
|
|
5923
|
-
const failItem = (nextOkVar, itemPosVar,
|
|
5924
|
-
if (!rec) return [
|
|
6129
|
+
const failItem = (nextOkVar, itemPosVar, breakStmt) => {
|
|
6130
|
+
if (!rec) return [`${ind(ctx)}if (!${nextOkVar}) ${breakStmt === "break" ? "break" : `{ ${breakStmt} }`}`];
|
|
5925
6131
|
const rr = v(ctx, "_rr");
|
|
5926
6132
|
return [
|
|
5927
6133
|
`${ind(ctx)}if (!${nextOkVar}) {`,
|
|
@@ -5929,10 +6135,24 @@ function emitSepBy(_p, def, ctx, pos) {
|
|
|
5929
6135
|
`${ind(ctx)} const ${rr} = _ctx._rec.scan(input, ${itemPosVar}, _ctx, ${scanSync}, ${exp})`,
|
|
5930
6136
|
`${ind(ctx)} ${arrV}.push(${rr}.error); _ctx._rec.capture(_ctx, ${rr}.error); ${curV} = ${rr}.end; continue`,
|
|
5931
6137
|
`${ind(ctx)} }`,
|
|
5932
|
-
`${ind(ctx)} ${
|
|
6138
|
+
`${ind(ctx)} ${breakStmt}`,
|
|
5933
6139
|
`${ind(ctx)}}`
|
|
5934
6140
|
];
|
|
5935
6141
|
};
|
|
6142
|
+
const itemFailBreak = (sepEndVar, sepRb, postSepRb) => def.trailing === void 0 ? `${sepRb}break` : `${postSepRb}${curV} = ${sepEndVar}; break`;
|
|
6143
|
+
const postSepMarks = () => {
|
|
6144
|
+
if (def.trailing === void 0 || !ctx.capturing) return { decl: [], rb: "" };
|
|
6145
|
+
const lv = v(ctx, "_tlv"), rw = v(ctx, "_trw"), tl = v(ctx, "_ttl"), lg = v(ctx, "_tlg");
|
|
6146
|
+
return {
|
|
6147
|
+
decl: [
|
|
6148
|
+
`${ind(ctx)}const ${lv} = _ctx._cstLeaves ? _ctx._cstLeaves.length : 0`,
|
|
6149
|
+
`${ind(ctx)}const ${rw} = _ctx._cstRawChildren ? _ctx._cstRawChildren.length : 0`,
|
|
6150
|
+
`${ind(ctx)}const ${tl} = _ctx._cstTriviaLog ? _ctx._cstTriviaLog.length : 0`,
|
|
6151
|
+
`${ind(ctx)}const ${lg} = _ctx._triviaLog ? _ctx._triviaLog.length : 0`
|
|
6152
|
+
],
|
|
6153
|
+
rb: `if (_ctx._cstLeaves) _ctx._cstLeaves.length = ${lv}; if (_ctx._cstRawChildren) _ctx._cstRawChildren.length = ${rw}; if (_ctx._cstTriviaLog) _ctx._cstTriviaLog.length = ${tl}; if (_ctx._triviaLog) _ctx._triviaLog.length = ${lg}; `
|
|
6154
|
+
};
|
|
6155
|
+
};
|
|
5936
6156
|
if (rec) {
|
|
5937
6157
|
const rr0 = v(ctx, "_rr0");
|
|
5938
6158
|
const didV = v(ctx, "_did");
|
|
@@ -5958,6 +6178,7 @@ function emitSepBy(_p, def, ctx, pos) {
|
|
|
5958
6178
|
);
|
|
5959
6179
|
ctx.indent++;
|
|
5960
6180
|
}
|
|
6181
|
+
if (def.max !== void 0) stmts.push(`${ind(ctx)}if (${arrV}.length >= ${def.max}) break`);
|
|
5961
6182
|
let sepAtPos = curV;
|
|
5962
6183
|
if (ctx.activeTrivia) {
|
|
5963
6184
|
if (ctx.capturing) {
|
|
@@ -5978,12 +6199,14 @@ function emitSepBy(_p, def, ctx, pos) {
|
|
|
5978
6199
|
const rollbackToSep = `if (_ctx._cstLeaves) _ctx._cstLeaves.length = ${markLv}; if (_ctx._cstRawChildren) _ctx._cstRawChildren.length = ${markV}; if (_ctx._cstTriviaLog) _ctx._cstTriviaLog.length = ${markTl}; if (_ctx._triviaLog) _ctx._triviaLog.length = ${markLog}; `;
|
|
5979
6200
|
const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
|
|
5980
6201
|
stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) { ${rollbackToSep}break }`);
|
|
6202
|
+
const post = postSepMarks();
|
|
6203
|
+
stmts.push(...post.decl);
|
|
5981
6204
|
const npV = v(ctx, "_np");
|
|
5982
6205
|
stmts.push(`${ind(ctx)}const ${npV} = ${capFn}(input, ${sepEnd}, _ctx, 1)`);
|
|
5983
6206
|
const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV, true);
|
|
5984
6207
|
stmts.push(
|
|
5985
6208
|
...nextStmts,
|
|
5986
|
-
...failItem(nextOk, npV, rollbackToSep,
|
|
6209
|
+
...failItem(nextOk, npV, itemFailBreak(sepEnd, rollbackToSep, post.rb)),
|
|
5987
6210
|
`${ind(ctx)}${arrV}.push(${nextVal})`,
|
|
5988
6211
|
`${ind(ctx)}${curV} = ${nextEnd}`
|
|
5989
6212
|
);
|
|
@@ -5999,7 +6222,7 @@ function emitSepBy(_p, def, ctx, pos) {
|
|
|
5999
6222
|
const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, npV, true);
|
|
6000
6223
|
stmts.push(
|
|
6001
6224
|
...nextStmts,
|
|
6002
|
-
...failItem(nextOk, npV, "",
|
|
6225
|
+
...failItem(nextOk, npV, itemFailBreak(sepEnd, "", "")),
|
|
6003
6226
|
`${ind(ctx)}${arrV}.push(${nextVal})`,
|
|
6004
6227
|
`${ind(ctx)}${curV} = ${nextEnd}`
|
|
6005
6228
|
);
|
|
@@ -6016,10 +6239,12 @@ function emitSepBy(_p, def, ctx, pos) {
|
|
|
6016
6239
|
const { stmts: sepStmts, okVar: sepOk, endVar: sepEnd } = emitFallible(def.separator, ctx, sepAtPos, true);
|
|
6017
6240
|
stmts.push(...sepStmts, `${ind(ctx)}if (!${sepOk}) break`);
|
|
6018
6241
|
const nextRb = markLv ? `if (_ctx._cstLeaves) _ctx._cstLeaves.length = ${markLv}; if (_ctx._cstRawChildren) _ctx._cstRawChildren.length = ${markRw}; ` : "";
|
|
6242
|
+
const post = postSepMarks();
|
|
6243
|
+
stmts.push(...post.decl);
|
|
6019
6244
|
const { stmts: nextStmts, okVar: nextOk, valVar: nextVal, endVar: nextEnd } = emitFallible(def.parser, ctx, sepEnd, true);
|
|
6020
6245
|
stmts.push(
|
|
6021
6246
|
...nextStmts,
|
|
6022
|
-
...failItem(nextOk, sepEnd, nextRb,
|
|
6247
|
+
...failItem(nextOk, sepEnd, itemFailBreak(sepEnd, nextRb, post.rb)),
|
|
6023
6248
|
`${ind(ctx)}${arrV}.push(${nextVal})`,
|
|
6024
6249
|
`${ind(ctx)}${curV} = ${nextEnd}`
|
|
6025
6250
|
);
|
|
@@ -6028,6 +6253,9 @@ function emitSepBy(_p, def, ctx, pos) {
|
|
|
6028
6253
|
stmts.push(`${ind(ctx)}}`);
|
|
6029
6254
|
ctx.indent--;
|
|
6030
6255
|
stmts.push(`${ind(ctx)}}`);
|
|
6256
|
+
if (def.min >= 1) {
|
|
6257
|
+
stmts.push(...emitIfFail(ctx, `${arrV}.length < ${def.min}`, failArrBody(ctx, deriveExpectedArr([def.parser]), curV)));
|
|
6258
|
+
}
|
|
6031
6259
|
return { stmts, valueVar: arrV, endVar: curV };
|
|
6032
6260
|
}
|
|
6033
6261
|
function emitScanTo(def, ctx, pos) {
|
|
@@ -6079,17 +6307,44 @@ function emitScanTo(def, ctx, pos) {
|
|
|
6079
6307
|
return { stmts, valueVar: valV, endVar: curV };
|
|
6080
6308
|
}
|
|
6081
6309
|
function emitNot(def, ctx, pos) {
|
|
6310
|
+
const sinksLive = !!ctx.capturing || !!ctx.recovery;
|
|
6311
|
+
const leaves = sinksLive ? v(ctx, "_ntl") : null;
|
|
6312
|
+
const raw = sinksLive ? v(ctx, "_ntr") : null;
|
|
6313
|
+
const tl = sinksLive ? v(ctx, "_ntt") : null;
|
|
6314
|
+
const log = sinksLive ? v(ctx, "_ntg") : null;
|
|
6315
|
+
const fields = sinksLive ? v(ctx, "_ntf") : null;
|
|
6316
|
+
const errors = sinksLive ? v(ctx, "_nte") : null;
|
|
6082
6317
|
const { stmts, okVar } = emitFallible(def.parser, ctx, pos, true);
|
|
6083
6318
|
const label2 = JSON.stringify(`not(${def.parser._tag})`);
|
|
6084
6319
|
return {
|
|
6085
6320
|
stmts: [
|
|
6321
|
+
...sinksLive ? [
|
|
6322
|
+
`${ind(ctx)}const ${leaves} = _ctx._cstLeaves?.length ?? 0, ${raw} = _ctx._cstRawChildren?.length ?? 0, ${tl} = _ctx._cstTriviaLog?.length ?? 0, ${log} = _ctx._triviaLog?.length ?? 0, ${fields} = _ctx._fields?.length ?? 0, ${errors} = _ctx._errors?.length ?? 0`
|
|
6323
|
+
] : [],
|
|
6086
6324
|
...stmts,
|
|
6325
|
+
...sinksLive ? [
|
|
6326
|
+
`${ind(ctx)}if (_ctx._cstLeaves) _ctx._cstLeaves.length = ${leaves}; if (_ctx._cstRawChildren) _ctx._cstRawChildren.length = ${raw}; if (_ctx._cstTriviaLog) _ctx._cstTriviaLog.length = ${tl}; if (_ctx._triviaLog) _ctx._triviaLog.length = ${log}; if (_ctx._fields) _ctx._fields.length = ${fields}; if (_ctx._errors) _ctx._errors.length = ${errors}`
|
|
6327
|
+
] : [],
|
|
6087
6328
|
...emitIfFail(ctx, okVar, failBody(ctx, label2, pos))
|
|
6088
6329
|
],
|
|
6089
6330
|
valueVar: "null",
|
|
6090
6331
|
endVar: pos
|
|
6091
6332
|
};
|
|
6092
6333
|
}
|
|
6334
|
+
function emitPeek(def, ctx, pos) {
|
|
6335
|
+
const probeCtx = { ...ctx, capturing: false, noHoist: true };
|
|
6336
|
+
const { stmts, okVar } = emitFallible(def.parser, probeCtx, pos, true);
|
|
6337
|
+
ctx.vars = probeCtx.vars;
|
|
6338
|
+
const label2 = JSON.stringify(`peek(${def.parser._tag})`);
|
|
6339
|
+
return {
|
|
6340
|
+
stmts: [
|
|
6341
|
+
...stmts,
|
|
6342
|
+
...emitIfFail(ctx, `!${okVar}`, failBody(ctx, label2, pos))
|
|
6343
|
+
],
|
|
6344
|
+
valueVar: "null",
|
|
6345
|
+
endVar: pos
|
|
6346
|
+
};
|
|
6347
|
+
}
|
|
6093
6348
|
function escapeTokenLiteral(value) {
|
|
6094
6349
|
return value.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
|
|
6095
6350
|
}
|
|
@@ -6377,9 +6632,9 @@ function emitRuntimeFallback(parser2, ctx, pos) {
|
|
|
6377
6632
|
return { stmts, valueVar: vv, endVar: ev };
|
|
6378
6633
|
}
|
|
6379
6634
|
function emitLazy(p, def, ctx, pos) {
|
|
6380
|
-
const
|
|
6635
|
+
const canonical2 = ctx.ruleNames?.get(p);
|
|
6381
6636
|
const usage = ctx.lazyUsage;
|
|
6382
|
-
if (!
|
|
6637
|
+
if (!canonical2 && usage && (usage.counts.get(p) ?? 0) <= 1 && !usage.recursive.has(p)) {
|
|
6383
6638
|
let resolved;
|
|
6384
6639
|
try {
|
|
6385
6640
|
resolved = def.thunk();
|
|
@@ -6389,7 +6644,7 @@ function emitLazy(p, def, ctx, pos) {
|
|
|
6389
6644
|
return emit(resolved, ctx, pos);
|
|
6390
6645
|
}
|
|
6391
6646
|
if (!ctx.namedParsers.has(p)) {
|
|
6392
|
-
const fnName2 =
|
|
6647
|
+
const fnName2 = canonical2 ?? `${nsp(ctx)}_pf${ctx.namedParsers.size}`;
|
|
6393
6648
|
ctx.namedParsers.set(p, fnName2);
|
|
6394
6649
|
let resolved;
|
|
6395
6650
|
try {
|
|
@@ -6672,6 +6927,8 @@ function emitDispatch(p, ctx, pos) {
|
|
|
6672
6927
|
}
|
|
6673
6928
|
case "not":
|
|
6674
6929
|
return emitNot(def, ctx, pos);
|
|
6930
|
+
case "peek":
|
|
6931
|
+
return emitPeek(def, ctx, pos);
|
|
6675
6932
|
case "node":
|
|
6676
6933
|
return emitNode(def, ctx, pos);
|
|
6677
6934
|
case "scanTo":
|
|
@@ -6760,6 +7017,7 @@ function hasNodeDef(p, seen = /* @__PURE__ */ new Set()) {
|
|
|
6760
7017
|
case "many":
|
|
6761
7018
|
case "oneOrMore":
|
|
6762
7019
|
case "not":
|
|
7020
|
+
case "peek":
|
|
6763
7021
|
case "transform":
|
|
6764
7022
|
return hasNodeDef(d.parser, seen);
|
|
6765
7023
|
case "skip":
|
|
@@ -6811,17 +7069,18 @@ function childrenOf(def) {
|
|
|
6811
7069
|
case "field":
|
|
6812
7070
|
case "grammar":
|
|
6813
7071
|
case "not":
|
|
7072
|
+
case "peek":
|
|
6814
7073
|
case "node":
|
|
6815
7074
|
case "withCtx":
|
|
6816
7075
|
case "expect":
|
|
6817
7076
|
return [def.parser];
|
|
6818
|
-
// emitMany's
|
|
6819
|
-
// (
|
|
6820
|
-
// emit() call
|
|
7077
|
+
// emitMany's min>=1 branch and emitSepBy both codegen `def.parser` more than
|
|
7078
|
+
// once (`min` mandatory matches, then again inside the repeat loop) — each is a
|
|
7079
|
+
// real emit() call site, so the usage analysis must see one edge per site or it
|
|
6821
7080
|
// undercounts a single-use `parser` ref as inline-safe when it's actually
|
|
6822
|
-
// referenced from
|
|
7081
|
+
// referenced from several positions within this one compiled function.
|
|
6823
7082
|
case "oneOrMore":
|
|
6824
|
-
return
|
|
7083
|
+
return Array.from({ length: def.min + 1 }, () => def.parser);
|
|
6825
7084
|
case "sepBy":
|
|
6826
7085
|
return [def.parser, def.parser, def.separator];
|
|
6827
7086
|
case "skip":
|
|
@@ -6967,12 +7226,41 @@ function resolveGatingLevel(opt) {
|
|
|
6967
7226
|
return "warn";
|
|
6968
7227
|
}
|
|
6969
7228
|
function runGatingDiagnostic(combinator, opt) {
|
|
7229
|
+
return reportGating(opt, (o) => analyzeGating(combinator, o));
|
|
7230
|
+
}
|
|
7231
|
+
function runGatingDiagnosticRules(ruleMap, opt) {
|
|
7232
|
+
return reportGating(opt, (o) => analyzeGatingRules(ruleMap, o));
|
|
7233
|
+
}
|
|
7234
|
+
function runFusedGatingDiagnostic(getRuleMaps, opt) {
|
|
7235
|
+
if (resolveGatingLevel(opt) === "off") return void 0;
|
|
7236
|
+
const ruleMaps = getRuleMaps();
|
|
7237
|
+
if (!ruleMaps.some((map) => hasExternalRuleRef(map))) return void 0;
|
|
7238
|
+
const winners = /* @__PURE__ */ new Map();
|
|
7239
|
+
for (const map of ruleMaps) for (const [name, rule] of map) {
|
|
7240
|
+
if (rule._def.tag === "lazy") {
|
|
7241
|
+
try {
|
|
7242
|
+
rule._def.thunk();
|
|
7243
|
+
} catch {
|
|
7244
|
+
continue;
|
|
7245
|
+
}
|
|
7246
|
+
}
|
|
7247
|
+
winners.set(name, rule);
|
|
7248
|
+
}
|
|
7249
|
+
const entries = [...winners];
|
|
7250
|
+
return reportGating(opt, (o) => {
|
|
7251
|
+
const deferredHere = new Set(analyzeGatingRules(entries, o).deferred.map((c) => c.id));
|
|
7252
|
+
const fused = analyzeGatingRules(entries, { ...o, resolveRef: (name) => winners.get(name) });
|
|
7253
|
+
return { ...fused, ungated: fused.ungated.filter((c) => deferredHere.has(c.id)), antiPatterns: [] };
|
|
7254
|
+
});
|
|
7255
|
+
}
|
|
7256
|
+
function reportGating(opt, analyze) {
|
|
6970
7257
|
const level = resolveGatingLevel(opt);
|
|
6971
7258
|
if (level === "off") return void 0;
|
|
6972
|
-
const
|
|
7259
|
+
const obj = opt !== null && typeof opt === "object" ? opt : void 0;
|
|
7260
|
+
const analyzeOpts = obj?.accept !== void 0 || obj?.entryName !== void 0 ? { ...obj.accept !== void 0 ? { accept: obj.accept } : {}, ...obj.entryName !== void 0 ? { entryName: obj.entryName } : {} } : void 0;
|
|
6973
7261
|
let report;
|
|
6974
7262
|
try {
|
|
6975
|
-
report =
|
|
7263
|
+
report = analyze(analyzeOpts);
|
|
6976
7264
|
} catch {
|
|
6977
7265
|
return void 0;
|
|
6978
7266
|
}
|
|
@@ -7108,6 +7396,7 @@ function publicRuleWrapperSource(rule, fnSource) {
|
|
|
7108
7396
|
return `Object.assign(${fnSource}, { _meta: { triviaKindLabels: ${JSON.stringify(labels)} } })`;
|
|
7109
7397
|
}
|
|
7110
7398
|
function compileRuleMap(ruleMap, opts) {
|
|
7399
|
+
runGatingDiagnosticRules(ruleMap, opts?.gating);
|
|
7111
7400
|
for (const [, rule] of ruleMap) markUnusedValues(rule);
|
|
7112
7401
|
const coverageWinners = opts?.coverage ? Object.fromEntries(ruleMap.filter(([, rule]) => rule._def.tag !== "lazy")) : void 0;
|
|
7113
7402
|
const grammarTrivia = opts?.trivia;
|
|
@@ -7226,6 +7515,7 @@ function compileRuleMap(ruleMap, opts) {
|
|
|
7226
7515
|
}
|
|
7227
7516
|
function compileLinkable(ruleMapArg, ns, opts) {
|
|
7228
7517
|
if (!ns) throw new Error("compileLinkable: ns must be a non-empty namespace");
|
|
7518
|
+
if (opts?.gating !== void 0) runGatingDiagnosticRules(ruleMapArg, opts.gating);
|
|
7229
7519
|
for (const [, rule] of ruleMapArg) markUnusedValues(rule);
|
|
7230
7520
|
const grammarTrivia = opts?.trivia ?? ruleMapArg.map(([, r]) => r._meta.grammarTrivia).find(Boolean);
|
|
7231
7521
|
const grammarScanSkip = opts?.scanSkip ?? ruleMapArg.map(([, r]) => r._meta.grammarScanSkip).find(Boolean);
|
|
@@ -7282,6 +7572,7 @@ function compileLinkable(ruleMapArg, ns, opts) {
|
|
|
7282
7572
|
fnNameToKey.set(fn, key);
|
|
7283
7573
|
}
|
|
7284
7574
|
ctx.ruleNames = ruleNames;
|
|
7575
|
+
const externalRefs = /* @__PURE__ */ new Set();
|
|
7285
7576
|
{
|
|
7286
7577
|
const scanned = /* @__PURE__ */ new Set();
|
|
7287
7578
|
const scanExternal = (p) => {
|
|
@@ -7297,11 +7588,14 @@ function compileLinkable(ruleMapArg, ns, opts) {
|
|
|
7297
7588
|
}
|
|
7298
7589
|
if (resolved === void 0) {
|
|
7299
7590
|
const name = p._ruleName;
|
|
7300
|
-
if (name
|
|
7301
|
-
|
|
7302
|
-
|
|
7303
|
-
|
|
7304
|
-
|
|
7591
|
+
if (name) {
|
|
7592
|
+
externalRefs.add(p);
|
|
7593
|
+
if (!ctx.namedParsers.has(p)) {
|
|
7594
|
+
assertRuleName(name);
|
|
7595
|
+
const fn = `_r_${name}`;
|
|
7596
|
+
ruleNames.set(p, fn);
|
|
7597
|
+
ctx.namedParsers.set(p, fn);
|
|
7598
|
+
}
|
|
7305
7599
|
}
|
|
7306
7600
|
return;
|
|
7307
7601
|
}
|
|
@@ -7414,12 +7708,32 @@ function compileLinkable(ruleMapArg, ns, opts) {
|
|
|
7414
7708
|
needsEmptyTl: !!ctx.needsEmptyTl,
|
|
7415
7709
|
needsHostReads: !!ctx.needsHostReads,
|
|
7416
7710
|
hasDirectBuilders: ruleMap.some(([, rule]) => hasDirectBuildDef(rule)),
|
|
7417
|
-
isRecognitionOnly: !hasSemanticReduction(ruleMap.map(([, rule]) => rule)),
|
|
7711
|
+
isRecognitionOnly: !hasSemanticReduction(ruleMap.map(([, rule]) => rule), externalRefs),
|
|
7418
7712
|
mfFns: mfSrcs ? [] : ctx.mapFns,
|
|
7419
7713
|
buildFns: buildSrcs ? [] : ctx.buildFns
|
|
7420
7714
|
};
|
|
7421
7715
|
}
|
|
7422
|
-
function
|
|
7716
|
+
function hasExternalRuleRef(ruleMap) {
|
|
7717
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7718
|
+
const visit = (p) => {
|
|
7719
|
+
if (seen.has(p)) return false;
|
|
7720
|
+
seen.add(p);
|
|
7721
|
+
const def = p._def;
|
|
7722
|
+
if (def.tag === "lazy") {
|
|
7723
|
+
let resolved;
|
|
7724
|
+
try {
|
|
7725
|
+
resolved = def.thunk();
|
|
7726
|
+
} catch {
|
|
7727
|
+
resolved = void 0;
|
|
7728
|
+
}
|
|
7729
|
+
if (resolved === void 0) return p._ruleName !== void 0;
|
|
7730
|
+
return visit(resolved);
|
|
7731
|
+
}
|
|
7732
|
+
return childrenOf(def).some(visit);
|
|
7733
|
+
};
|
|
7734
|
+
return ruleMap.some(([, rule]) => visit(rule));
|
|
7735
|
+
}
|
|
7736
|
+
function hasSemanticReduction(roots, externalRefs) {
|
|
7423
7737
|
const seen = /* @__PURE__ */ new Set();
|
|
7424
7738
|
const visit = (parser2) => {
|
|
7425
7739
|
if (seen.has(parser2)) return false;
|
|
@@ -7430,6 +7744,7 @@ function hasSemanticReduction(roots) {
|
|
|
7430
7744
|
if (def.tag === "guard" || def.tag === "withCtx") return true;
|
|
7431
7745
|
if (def.tag === "node" && def.build !== void 0) return true;
|
|
7432
7746
|
if (def.tag === "lazy") {
|
|
7747
|
+
if (externalRefs?.has(parser2)) return false;
|
|
7433
7748
|
try {
|
|
7434
7749
|
return visit(def.thunk());
|
|
7435
7750
|
} catch {
|
|
@@ -7521,6 +7836,7 @@ function childrenOf2(def) {
|
|
|
7521
7836
|
case "label":
|
|
7522
7837
|
case "field":
|
|
7523
7838
|
case "not":
|
|
7839
|
+
case "peek":
|
|
7524
7840
|
case "node":
|
|
7525
7841
|
case "withCtx":
|
|
7526
7842
|
case "expect":
|
|
@@ -7554,9 +7870,16 @@ function lazyTarget(c) {
|
|
|
7554
7870
|
}
|
|
7555
7871
|
var Unserializable = class extends Error {
|
|
7556
7872
|
};
|
|
7557
|
-
function
|
|
7873
|
+
function repeatOpts(min, max, trailing) {
|
|
7874
|
+
const parts = [];
|
|
7875
|
+
if (min !== 0) parts.push(`min: ${min}`);
|
|
7876
|
+
if (max !== void 0) parts.push(`max: ${max}`);
|
|
7877
|
+
if (trailing !== void 0) parts.push(`trailing: ${JSON.stringify(trailing)}`);
|
|
7878
|
+
return parts.length === 0 ? "" : `, { ${parts.join(", ")} }`;
|
|
7879
|
+
}
|
|
7880
|
+
function serializeRuleMap(ruleMap, scanSkip) {
|
|
7558
7881
|
try {
|
|
7559
|
-
return new Serializer(ruleMap).run();
|
|
7882
|
+
return new Serializer(ruleMap, scanSkip ?? []).run();
|
|
7560
7883
|
} catch (e) {
|
|
7561
7884
|
if (e instanceof Unserializable) {
|
|
7562
7885
|
if (process.env.PARSEMAN_IR_DEBUG) console.error(`[ir] fallback: ${e.message}`);
|
|
@@ -7657,6 +7980,7 @@ function evalRuleMapIR(ir) {
|
|
|
7657
7980
|
"optional",
|
|
7658
7981
|
"sepBy",
|
|
7659
7982
|
"not",
|
|
7983
|
+
"peek",
|
|
7660
7984
|
"node",
|
|
7661
7985
|
"parser",
|
|
7662
7986
|
"scanTo",
|
|
@@ -7689,6 +8013,7 @@ function evalRuleMapIR(ir) {
|
|
|
7689
8013
|
optional,
|
|
7690
8014
|
sepBy,
|
|
7691
8015
|
not,
|
|
8016
|
+
peek,
|
|
7692
8017
|
node,
|
|
7693
8018
|
parser,
|
|
7694
8019
|
scanTo,
|
|
@@ -7717,10 +8042,28 @@ var Serializer = class {
|
|
|
7717
8042
|
decls = [];
|
|
7718
8043
|
ruleValues = /* @__PURE__ */ new Set();
|
|
7719
8044
|
ruleMap;
|
|
7720
|
-
|
|
8045
|
+
scanSkip;
|
|
8046
|
+
constructor(ruleMap, scanSkip = []) {
|
|
7721
8047
|
this.ruleMap = ruleMap;
|
|
8048
|
+
this.scanSkip = scanSkip;
|
|
7722
8049
|
for (const [, c] of ruleMap) this.ruleValues.add(this.body(c));
|
|
7723
8050
|
}
|
|
8051
|
+
/** A grammar-level `scanSkip` unit is an opaque TERMINAL (a string, a balanced
|
|
8052
|
+
* bracket) — it must not reach a grammar-rule ref (`g[name]`), because the
|
|
8053
|
+
* serialized `scanSkip` array sits in the `rules({ scanSkip })` OPTIONS position,
|
|
8054
|
+
* outside the `(g) => …` factory where `g` is bound. A unit that does reference a
|
|
8055
|
+
* rule can't be carried in options; bail to the full-pieces fallback. */
|
|
8056
|
+
assertNoRuleRef(c, seen) {
|
|
8057
|
+
if (seen.has(c)) return;
|
|
8058
|
+
seen.add(c);
|
|
8059
|
+
if (c._def.tag === "lazy") {
|
|
8060
|
+
if (ruleNameOf2(c) !== void 0) throw new Unserializable("scanSkip unit references a grammar rule");
|
|
8061
|
+
const target = lazyTarget(c);
|
|
8062
|
+
if (target) this.assertNoRuleRef(target, seen);
|
|
8063
|
+
return;
|
|
8064
|
+
}
|
|
8065
|
+
for (const child of childrenOf2(c._def)) this.assertNoRuleRef(child, seen);
|
|
8066
|
+
}
|
|
7724
8067
|
/** A `rules()` map wraps each rule value in a named `lazy` proxy; the real body is
|
|
7725
8068
|
* its target (compileLinkable unwraps the same way). A ref INSIDE a body stays a
|
|
7726
8069
|
* `g[name]` ref — only the top-level entry is unwrapped. */
|
|
@@ -7729,21 +8072,38 @@ var Serializer = class {
|
|
|
7729
8072
|
}
|
|
7730
8073
|
run() {
|
|
7731
8074
|
for (const [, c] of this.ruleMap) this.analyze(this.body(c), /* @__PURE__ */ new Set());
|
|
8075
|
+
for (const c of this.scanSkip) {
|
|
8076
|
+
this.assertNoRuleRef(c, /* @__PURE__ */ new Set());
|
|
8077
|
+
this.analyze(c, /* @__PURE__ */ new Set());
|
|
8078
|
+
}
|
|
7732
8079
|
let n = 0;
|
|
7733
8080
|
for (const [c, count] of this.counts) {
|
|
7734
8081
|
if (this.ruleValues.has(c)) continue;
|
|
7735
8082
|
if (count >= 2 || this.selfRef.has(c)) this.constName.set(c, `_s${n++}`);
|
|
7736
8083
|
}
|
|
8084
|
+
const outerDecls = [];
|
|
8085
|
+
const innerDecls = [];
|
|
8086
|
+
let optionsArg = "";
|
|
8087
|
+
if (this.scanSkip.length > 0) {
|
|
8088
|
+
this.decls = outerDecls;
|
|
8089
|
+
const units = this.scanSkip.map((c) => this.ref(c, null));
|
|
8090
|
+
optionsArg = `{ scanSkip: [${units.join(", ")}] }, `;
|
|
8091
|
+
}
|
|
8092
|
+
this.decls = innerDecls;
|
|
7737
8093
|
const body = this.ruleMap.map(([name, c]) => ` ${JSON.stringify(name)}: ${this.wrap(this.body(c))}`).join(",\n");
|
|
7738
|
-
|
|
8094
|
+
const factory = innerDecls.length === 0 ? `(g) => ({
|
|
7739
8095
|
${body}
|
|
7740
|
-
}))
|
|
7741
|
-
|
|
7742
|
-
${this.decls.map((d) => " " + d).join("\n")}
|
|
8096
|
+
})` : `(g) => {
|
|
8097
|
+
${innerDecls.map((d) => " " + d).join("\n")}
|
|
7743
8098
|
return ({
|
|
7744
8099
|
${body}
|
|
7745
8100
|
})
|
|
7746
|
-
}
|
|
8101
|
+
}`;
|
|
8102
|
+
const call = `rules(${optionsArg}${factory})`;
|
|
8103
|
+
return outerDecls.length === 0 ? call : `(() => {
|
|
8104
|
+
${outerDecls.map((d) => " " + d).join("\n")}
|
|
8105
|
+
return ${call}
|
|
8106
|
+
})()`;
|
|
7747
8107
|
}
|
|
7748
8108
|
/** Count identity references and flag self-referential subtrees. `active` is the
|
|
7749
8109
|
* set of combinators on the current DFS path (to detect a lazy pointing back). */
|
|
@@ -7828,17 +8188,21 @@ ${body}
|
|
|
7828
8188
|
return `_gch([${items.join(", ")}])`;
|
|
7829
8189
|
}
|
|
7830
8190
|
case "many":
|
|
7831
|
-
return `many(${kid(def.parser)})`;
|
|
8191
|
+
return `many(${kid(def.parser)}${repeatOpts(def.min, def.max)})`;
|
|
8192
|
+
// A `oneOrMore` def with min > 1 came from `many(x, { min: n })`; round-trip
|
|
8193
|
+
// it through `many` so the bounds survive (oneOrMore defaults min to 1).
|
|
7832
8194
|
case "oneOrMore":
|
|
7833
|
-
return `oneOrMore(${kid(def.parser)})`;
|
|
8195
|
+
return def.min === 1 && def.max === void 0 ? `oneOrMore(${kid(def.parser)})` : `many(${kid(def.parser)}${repeatOpts(def.min, def.max)})`;
|
|
7834
8196
|
case "optional":
|
|
7835
8197
|
return `optional(${kid(def.parser)})`;
|
|
7836
8198
|
case "attempt":
|
|
7837
8199
|
return `attempt(${kid(def.parser)})`;
|
|
7838
8200
|
case "sepBy":
|
|
7839
|
-
return `sepBy(${kid(def.parser)}, ${kid(def.separator)})`;
|
|
8201
|
+
return `sepBy(${kid(def.parser)}, ${kid(def.separator)}${repeatOpts(def.min, def.max, def.trailing)})`;
|
|
7840
8202
|
case "not":
|
|
7841
8203
|
return `not(${kid(def.parser)})`;
|
|
8204
|
+
case "peek":
|
|
8205
|
+
return `peek(${kid(def.parser)})`;
|
|
7842
8206
|
case "trivia":
|
|
7843
8207
|
return `trivia(${kid(def.parser)})`;
|
|
7844
8208
|
case "token":
|
|
@@ -8274,15 +8638,12 @@ var SUPPORTED = {
|
|
|
8274
8638
|
literal: (...a) => literal(a[0], a[1]),
|
|
8275
8639
|
regex: (...a) => regex(a[0], a[1]),
|
|
8276
8640
|
keywords: (...a) => keywords(a[0], a[1]),
|
|
8277
|
-
word: (...a) => word(a[0], a[1]),
|
|
8641
|
+
word: (...a) => word(a[0], a[1], a[2]),
|
|
8278
8642
|
sequence: (...a) => sequence(...a),
|
|
8279
8643
|
choice: (...a) => choice(...a),
|
|
8280
8644
|
attempt: (...a) => attempt(a[0]),
|
|
8281
|
-
many: (...a) => many(a[0]),
|
|
8282
|
-
oneOrMore: (...a) => oneOrMore(a[0]),
|
|
8283
8645
|
optional: (...a) => optional(a[0]),
|
|
8284
8646
|
skip: (...a) => skip(a[0], a[1]),
|
|
8285
|
-
sepBy: (...a) => sepBy(a[0], a[1]),
|
|
8286
8647
|
trivia: (...a) => trivia(a[0]),
|
|
8287
8648
|
label: (...a) => label(a[0], a[1]),
|
|
8288
8649
|
field: (...a) => field(a[0], a[1]),
|
|
@@ -8458,8 +8819,8 @@ function exprToCombi(node2, scope, code, mfs) {
|
|
|
8458
8819
|
return null;
|
|
8459
8820
|
}
|
|
8460
8821
|
}
|
|
8461
|
-
if (callee.name === "sepBy") {
|
|
8462
|
-
const [itemArg, sepArg] = node2.arguments;
|
|
8822
|
+
if (callee.name === "sepBy" || callee.name === "oneOrMoreSep") {
|
|
8823
|
+
const [itemArg, sepArg, optsArg] = node2.arguments;
|
|
8463
8824
|
if (!itemArg || !sepArg || itemArg.type === "SpreadElement" || sepArg.type === "SpreadElement") return null;
|
|
8464
8825
|
const itemMfs = [];
|
|
8465
8826
|
const itemCombi = anyValue(itemArg, scope, code, itemMfs);
|
|
@@ -8467,25 +8828,42 @@ function exprToCombi(node2, scope, code, mfs) {
|
|
|
8467
8828
|
const sepMfs = [];
|
|
8468
8829
|
const sepCombi = anyValue(sepArg, scope, code, sepMfs);
|
|
8469
8830
|
if (!isCombinator(sepCombi)) return null;
|
|
8831
|
+
let opts;
|
|
8832
|
+
if (optsArg) {
|
|
8833
|
+
if (optsArg.type === "SpreadElement") return null;
|
|
8834
|
+
const v2 = anyValue(optsArg, scope, code, []);
|
|
8835
|
+
if (!v2 || typeof v2 !== "object") return null;
|
|
8836
|
+
opts = v2;
|
|
8837
|
+
}
|
|
8470
8838
|
if (mfs) mfs.push(...itemMfs, ...sepMfs, ...itemMfs);
|
|
8471
8839
|
try {
|
|
8472
|
-
return sepBy(itemCombi, sepCombi);
|
|
8840
|
+
return callee.name === "sepBy" ? sepBy(itemCombi, sepCombi, opts) : oneOrMoreSep(itemCombi, sepCombi, opts);
|
|
8473
8841
|
} catch {
|
|
8474
8842
|
return null;
|
|
8475
8843
|
}
|
|
8476
8844
|
}
|
|
8477
|
-
if (callee.name === "oneOrMore") {
|
|
8478
|
-
const [itemArg] = node2.arguments;
|
|
8845
|
+
if (callee.name === "oneOrMore" || callee.name === "many") {
|
|
8846
|
+
const [itemArg, optsArg] = node2.arguments;
|
|
8479
8847
|
if (!itemArg || itemArg.type === "SpreadElement") return null;
|
|
8480
8848
|
const itemMfs = [];
|
|
8481
8849
|
const itemCombi = anyValue(itemArg, scope, code, itemMfs);
|
|
8482
8850
|
if (!isCombinator(itemCombi)) return null;
|
|
8483
|
-
|
|
8851
|
+
let opts;
|
|
8852
|
+
if (optsArg) {
|
|
8853
|
+
if (optsArg.type === "SpreadElement") return null;
|
|
8854
|
+
const v2 = anyValue(optsArg, scope, code, []);
|
|
8855
|
+
if (!v2 || typeof v2 !== "object") return null;
|
|
8856
|
+
opts = v2;
|
|
8857
|
+
}
|
|
8858
|
+
let combi;
|
|
8484
8859
|
try {
|
|
8485
|
-
|
|
8860
|
+
combi = callee.name === "many" ? many(itemCombi, opts) : oneOrMore(itemCombi, opts);
|
|
8486
8861
|
} catch {
|
|
8487
8862
|
return null;
|
|
8488
8863
|
}
|
|
8864
|
+
const min = combi._def.tag === "oneOrMore" ? combi._def.min : 0;
|
|
8865
|
+
if (mfs) for (let i = 0; i <= min; i++) mfs.push(...itemMfs);
|
|
8866
|
+
return combi;
|
|
8489
8867
|
}
|
|
8490
8868
|
if (callee.name === "not") {
|
|
8491
8869
|
const [innerArg] = node2.arguments;
|
|
@@ -8498,6 +8876,17 @@ function exprToCombi(node2, scope, code, mfs) {
|
|
|
8498
8876
|
return null;
|
|
8499
8877
|
}
|
|
8500
8878
|
}
|
|
8879
|
+
if (callee.name === "peek") {
|
|
8880
|
+
const [innerArg] = node2.arguments;
|
|
8881
|
+
if (!innerArg || innerArg.type === "SpreadElement") return null;
|
|
8882
|
+
const inner = anyValue(innerArg, scope, code, mfs);
|
|
8883
|
+
if (!isCombinator(inner)) return null;
|
|
8884
|
+
try {
|
|
8885
|
+
return peek(inner);
|
|
8886
|
+
} catch {
|
|
8887
|
+
return null;
|
|
8888
|
+
}
|
|
8889
|
+
}
|
|
8501
8890
|
if (callee.name === "balanced") {
|
|
8502
8891
|
const [openArg, closeArg, optsArg] = node2.arguments;
|
|
8503
8892
|
if (!openArg || !closeArg || openArg.type === "SpreadElement" || closeArg.type === "SpreadElement") return null;
|
|
@@ -9127,6 +9516,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
|
|
|
9127
9516
|
const warnings = [];
|
|
9128
9517
|
beginLoweringCapture();
|
|
9129
9518
|
let anyUnresolved = false;
|
|
9519
|
+
let keepMacroImport = false;
|
|
9130
9520
|
let runtimeComposeFallback = false;
|
|
9131
9521
|
let ruleMapHolderCounter = 0;
|
|
9132
9522
|
const lineOf = (pos) => {
|
|
@@ -9161,17 +9551,21 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
|
|
|
9161
9551
|
const gTrivia = triviaValue ? evaluateExpr(triviaValue, scope, code, []) : void 0;
|
|
9162
9552
|
const scanSkipValue = optionValue("scanSkip");
|
|
9163
9553
|
const gScanSkip = scanSkipValue ? evaluateCombinatorArray(scanSkipValue, scope, code) ?? void 0 : void 0;
|
|
9554
|
+
if (gScanSkip) {
|
|
9555
|
+
for (const rule of ruleMap.values()) {
|
|
9556
|
+
if (rule && !rule._meta.isTrivia) {
|
|
9557
|
+
;
|
|
9558
|
+
rule._meta.grammarScanSkip = gScanSkip;
|
|
9559
|
+
}
|
|
9560
|
+
}
|
|
9561
|
+
}
|
|
9164
9562
|
return { ruleMap, ...gTrivia ? { trivia: gTrivia } : {}, ...gScanSkip ? { scanSkip: gScanSkip } : {} };
|
|
9165
9563
|
};
|
|
9166
9564
|
const compileRulesFactory = (init, label2) => {
|
|
9167
9565
|
const evaluated = evaluateRulesFactory(init, label2);
|
|
9168
9566
|
if (!evaluated) return null;
|
|
9169
9567
|
const compiled = compileRuleMap([...evaluated.ruleMap], { ...evaluated.trivia ? { trivia: evaluated.trivia } : {}, ...evaluated.scanSkip ? { scanSkip: evaluated.scanSkip } : {}, recovery, coverage: grammarCoverage });
|
|
9170
|
-
|
|
9171
|
-
warn(init.start, `${label2}: rule map couldn't be inlined`);
|
|
9172
|
-
return null;
|
|
9173
|
-
}
|
|
9174
|
-
return { replacement: compiled.replacement, ruleMap: evaluated.ruleMap, ...compiled.coverageDefinitions ? { coverageDefinitions: compiled.coverageDefinitions } : {}, ...evaluated.trivia ? { trivia: evaluated.trivia } : {}, ...evaluated.scanSkip ? { scanSkip: evaluated.scanSkip } : {} };
|
|
9568
|
+
return { replacement: compiled?.replacement ?? null, ruleMap: evaluated.ruleMap, ...compiled?.coverageDefinitions ? { coverageDefinitions: compiled.coverageDefinitions } : {}, ...evaluated.trivia ? { trivia: evaluated.trivia } : {}, ...evaluated.scanSkip ? { scanSkip: evaluated.scanSkip } : {} };
|
|
9175
9569
|
};
|
|
9176
9570
|
const isRulesCall = (init) => init.type === "CallExpression" && init.callee.type === "Identifier" && init.callee.name === "rules";
|
|
9177
9571
|
const isComposeCall = (init) => init.type === "CallExpression" && init.callee.type === "Identifier" && init.callee.name === "compose";
|
|
@@ -9266,6 +9660,17 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
|
|
|
9266
9660
|
}
|
|
9267
9661
|
return out;
|
|
9268
9662
|
};
|
|
9663
|
+
const carriedRuleMaps = (items) => {
|
|
9664
|
+
const out = [];
|
|
9665
|
+
const add = (it) => {
|
|
9666
|
+
if (isIR(it)) out.push(evalRuleMapIR(it.ir));
|
|
9667
|
+
};
|
|
9668
|
+
for (const it of items) {
|
|
9669
|
+
if (isSpread(it)) for (const p of importedPieces(it.__spreadLocal) ?? []) add(p);
|
|
9670
|
+
else add(it);
|
|
9671
|
+
}
|
|
9672
|
+
return out;
|
|
9673
|
+
};
|
|
9269
9674
|
const materializeLeafCoverage = (items, localRules, localNs, composing, captureTerminals = false) => {
|
|
9270
9675
|
const sources = [];
|
|
9271
9676
|
const add = (item) => {
|
|
@@ -9297,12 +9702,12 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
|
|
|
9297
9702
|
}
|
|
9298
9703
|
return pieces;
|
|
9299
9704
|
};
|
|
9300
|
-
const localCarried = (rm, label2, composing) => {
|
|
9705
|
+
const localCarried = (rm, label2, composing, scanSkip) => {
|
|
9301
9706
|
const entries = [...rm];
|
|
9302
9707
|
const ns = nsFor(label2);
|
|
9303
|
-
const ir = serializeRuleMap(entries);
|
|
9708
|
+
const ir = serializeRuleMap(entries, scanSkip);
|
|
9304
9709
|
if (ir) return { carried: [{ ns, ir }] };
|
|
9305
|
-
const p = compileLinkable(entries, ns, { ...composing ? { trivia: composing } : {}, recovery });
|
|
9710
|
+
const p = compileLinkable(entries, ns, { ...composing ? { trivia: composing } : {}, ...scanSkip ? { scanSkip } : {}, recovery });
|
|
9306
9711
|
return p ? { carried: [p] } : null;
|
|
9307
9712
|
};
|
|
9308
9713
|
const argPieces = (arg, label2, composing) => {
|
|
@@ -9327,18 +9732,13 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
|
|
|
9327
9732
|
}
|
|
9328
9733
|
}
|
|
9329
9734
|
if (isRulesCall(arg)) {
|
|
9330
|
-
const
|
|
9331
|
-
|
|
9332
|
-
const a1 = rulesArgs[1];
|
|
9333
|
-
const optionsFirst = a0?.type === "ObjectExpression";
|
|
9334
|
-
const factory = optionsFirst ? a1 : a0;
|
|
9335
|
-
const rm = factory ? evaluateParserFactory(factory, scope, code, []) : null;
|
|
9336
|
-
return rm ? localCarried(rm, label2, composing) : null;
|
|
9735
|
+
const evaluated = evaluateRulesFactory(arg, label2);
|
|
9736
|
+
return evaluated ? localCarried(evaluated.ruleMap, label2, composing, evaluated.scanSkip) : null;
|
|
9337
9737
|
}
|
|
9338
9738
|
if (arg.type === "Identifier") {
|
|
9339
9739
|
const name = arg.name;
|
|
9340
9740
|
const rm = localRuleMaps.get(name);
|
|
9341
|
-
if (rm) return localCarried(rm, label2, composing);
|
|
9741
|
+
if (rm) return localCarried(rm, label2, composing, localGrammarScanSkip.get(name));
|
|
9342
9742
|
const composed = localComposedCarried.get(name);
|
|
9343
9743
|
if (composed) return { carried: composed };
|
|
9344
9744
|
const pieces = importedPieces(name);
|
|
@@ -9404,6 +9804,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
|
|
|
9404
9804
|
}
|
|
9405
9805
|
carried.push(...r.carried);
|
|
9406
9806
|
}
|
|
9807
|
+
runFusedGatingDiagnostic(() => carriedRuleMaps(carried));
|
|
9407
9808
|
const pieces = materializeCarried(carried, composing);
|
|
9408
9809
|
try {
|
|
9409
9810
|
return { replacement: emitFusedSource(pieces), carried, ...composing ? { trivia: composing } : {} };
|
|
@@ -9452,6 +9853,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
|
|
|
9452
9853
|
}
|
|
9453
9854
|
try {
|
|
9454
9855
|
const localNs = nsFor(`composeLeaf${init.start}`);
|
|
9856
|
+
runFusedGatingDiagnostic(() => [...carriedRuleMaps(carried), [...localRules]]);
|
|
9455
9857
|
const plainLocalPiece = compileLinkable([...localRules], localNs, { ...composing ? { trivia: composing } : {}, ...localScanSkip ? { scanSkip: localScanSkip } : {}, recovery });
|
|
9456
9858
|
if (!plainLocalPiece) {
|
|
9457
9859
|
warn(init.start, "composeLeaf(): local rules could not be statically compiled");
|
|
@@ -9540,7 +9942,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
|
|
|
9540
9942
|
const refEntry = scope.get(varName);
|
|
9541
9943
|
const refCombi = refEntry?.combi ?? null;
|
|
9542
9944
|
if (refCombi) {
|
|
9543
|
-
const compiled2 = compile(refCombi, void 0, { recovery, coverage: grammarCoverage });
|
|
9945
|
+
const compiled2 = compile(refCombi, void 0, { recovery, coverage: grammarCoverage, gating: { entryName: varName } });
|
|
9544
9946
|
if (compiled2.inlineExpression === null) {
|
|
9545
9947
|
warn(init.start, `"${varName}" is a ref() that couldn't be inlined (was .define() called with a static combinator?)`);
|
|
9546
9948
|
continue;
|
|
@@ -9555,17 +9957,26 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
|
|
|
9555
9957
|
localRuleMaps.set(varName, compiledRules.ruleMap);
|
|
9556
9958
|
if (compiledRules.trivia) localGrammarTrivia.set(varName, compiledRules.trivia);
|
|
9557
9959
|
if (compiledRules.scanSkip) localGrammarScanSkip.set(varName, compiledRules.scanSkip);
|
|
9960
|
+
const ns = nsFor(varName);
|
|
9961
|
+
const pieces = exportPrefix || compiledRules.replacement === null ? compileLinkable([...compiledRules.ruleMap], ns, {
|
|
9962
|
+
...compiledRules.scanSkip ? { scanSkip: compiledRules.scanSkip } : {},
|
|
9963
|
+
recovery
|
|
9964
|
+
}) : null;
|
|
9965
|
+
if (compiledRules.replacement === null) {
|
|
9966
|
+
if (!pieces || !hasExternalRuleRef([...compiledRules.ruleMap])) {
|
|
9967
|
+
warn(init.start, `${varName}: rule map couldn't be inlined`);
|
|
9968
|
+
continue;
|
|
9969
|
+
}
|
|
9970
|
+
keepMacroImport = true;
|
|
9971
|
+
}
|
|
9972
|
+
const source = compiledRules.replacement ?? code.slice(init.start, init.end);
|
|
9558
9973
|
let replacement = withCoverageDefinitions(
|
|
9559
|
-
|
|
9560
|
-
compiledRules.coverageDefinitions?.length ? compiledRules.coverageDefinitions : emittedCoverageDefinitions(
|
|
9974
|
+
source,
|
|
9975
|
+
compiledRules.coverageDefinitions?.length ? compiledRules.coverageDefinitions : emittedCoverageDefinitions(source)
|
|
9561
9976
|
);
|
|
9562
|
-
if (exportPrefix) {
|
|
9563
|
-
const
|
|
9564
|
-
|
|
9565
|
-
if (pieces && !pieces.mfFns.length && !pieces.buildFns.length) {
|
|
9566
|
-
const ir = serializeRuleMap([...compiledRules.ruleMap]);
|
|
9567
|
-
replacement = withCarriedPieces(replacement, [ir ? { ns, ir } : pieces]);
|
|
9568
|
-
}
|
|
9977
|
+
if (exportPrefix && pieces && !pieces.mfFns.length && !pieces.buildFns.length) {
|
|
9978
|
+
const ir = serializeRuleMap([...compiledRules.ruleMap], compiledRules.scanSkip);
|
|
9979
|
+
replacement = withCarriedPieces(replacement, [ir ? { ns, ir } : pieces]);
|
|
9569
9980
|
}
|
|
9570
9981
|
replacements.push({ start: init.start, end: init.end, replacement });
|
|
9571
9982
|
continue;
|
|
@@ -9608,7 +10019,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
|
|
|
9608
10019
|
warn(init.start, `"${varName}" references a parseman macro import but isn't a statically-evaluable combinator`);
|
|
9609
10020
|
continue;
|
|
9610
10021
|
}
|
|
9611
|
-
const compiled = compile(parser2, void 0, { recovery, coverage: grammarCoverage });
|
|
10022
|
+
const compiled = compile(parser2, void 0, { recovery, coverage: grammarCoverage, gating: { entryName: varName } });
|
|
9612
10023
|
if (compiled.inlineExpression === null) {
|
|
9613
10024
|
warn(init.start, `"${varName}" couldn't be inlined (likely closes over a runtime value)`);
|
|
9614
10025
|
continue;
|
|
@@ -9627,6 +10038,10 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
|
|
|
9627
10038
|
}
|
|
9628
10039
|
const compiledRules = compileRulesFactory(init, "{ \u2026 }");
|
|
9629
10040
|
if (!compiledRules) continue;
|
|
10041
|
+
if (compiledRules.replacement === null) {
|
|
10042
|
+
warn(init.start, `{ \u2026 }: rule map couldn't be inlined`);
|
|
10043
|
+
continue;
|
|
10044
|
+
}
|
|
9630
10045
|
const pattern = d.id;
|
|
9631
10046
|
const bindings = [];
|
|
9632
10047
|
let allOk = true;
|
|
@@ -9668,7 +10083,7 @@ function transformMacro(code, id, moduleAliases = /* @__PURE__ */ new Set([PARSE
|
|
|
9668
10083
|
}
|
|
9669
10084
|
if (replacements.length === 0 && warnings.length === 0) return null;
|
|
9670
10085
|
for (const imp of macroImports) {
|
|
9671
|
-
imp.fullyResolved = !anyUnresolved;
|
|
10086
|
+
imp.fullyResolved = !anyUnresolved && !keepMacroImport;
|
|
9672
10087
|
}
|
|
9673
10088
|
const ms = new MagicString(code);
|
|
9674
10089
|
if (!anyUnresolved) {
|