parseman 0.50.3 → 0.50.5

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/index.js CHANGED
@@ -35,10 +35,20 @@ function classifyFinalChoice(arms, resolve) {
35
35
  }
36
36
  return { exclusive, firstSets, nullable: nullable2 };
37
37
  }
38
- function resolveNamedRef(p, resolve) {
38
+ function winnerForRef(p, resolve) {
39
39
  if (resolve === void 0) return void 0;
40
40
  const name = p._ruleName;
41
- return name === void 0 ? void 0 : resolve(name);
41
+ const w = name === void 0 ? void 0 : resolve(name);
42
+ if (w === void 0 || w === p) return void 0;
43
+ let cur = w;
44
+ for (const seen = /* @__PURE__ */ new Set(); !seen.has(cur); ) {
45
+ if (cur === p) return void 0;
46
+ seen.add(cur);
47
+ const cd = cur._def;
48
+ if (cd.tag !== "grammar" && cd.tag !== "trivia") return w;
49
+ cur = cd.parser;
50
+ }
51
+ return w;
42
52
  }
43
53
  function union(a, b) {
44
54
  if (a.kind === "any" || b.kind === "any") return { kind: "any" };
@@ -136,13 +146,15 @@ function matchesEmptyBody(p, seen, resolve) {
136
146
  case "grammar":
137
147
  case "recover":
138
148
  return me(d.parser);
139
- case "lazy":
149
+ case "lazy": {
150
+ const w = winnerForRef(p, resolve);
151
+ if (w !== void 0) return me(w);
140
152
  try {
141
153
  return me(d.thunk());
142
154
  } catch {
143
- const t = resolveNamedRef(p, resolve);
144
- return t ? me(t) : true;
155
+ return true;
145
156
  }
157
+ }
146
158
  default:
147
159
  return true;
148
160
  }
@@ -206,13 +218,15 @@ function firstSetBody(p, seen, resolve) {
206
218
  // terminals: no refs, cached set is exact
207
219
  case "dispatch":
208
220
  return fs(d.selector);
209
- case "lazy":
221
+ case "lazy": {
222
+ const w = winnerForRef(p, resolve);
223
+ if (w !== void 0) return fs(w);
210
224
  try {
211
225
  return fs(d.thunk());
212
226
  } catch {
213
- const t = resolveNamedRef(p, resolve);
214
- return t ? fs(t) : any();
227
+ return any();
215
228
  }
229
+ }
216
230
  case "choice": {
217
231
  let out = empty();
218
232
  for (const arm of d.parsers) out = union(out, fs(arm));
@@ -8066,7 +8080,7 @@ var Encoder = class {
8066
8080
  d.tags === void 0 ? -1 : this.constant(d.tags)
8067
8081
  );
8068
8082
  if (matchesEmpty(d.parser)) return body;
8069
- const fs = d.parser._meta.firstSet;
8083
+ const fs = d.parser._def.tag === "lazy" ? firstSetOf(d.parser, /* @__PURE__ */ new Set(), this.refResolver()) : d.parser._meta.firstSet;
8070
8084
  if (fs.kind === "any") return body;
8071
8085
  const cls = this.charClass(fs);
8072
8086
  if (cls < 0) return body;
@@ -8180,7 +8194,7 @@ var Encoder = class {
8180
8194
  case "attempt": {
8181
8195
  const inner = this.emit(OP_ATTEMPT, this.node(d.parser).ip);
8182
8196
  if (!this.failureNeedsRollback(d.parser, void 0)) this.failureRollbackCleanSites.add(inner);
8183
- const fs = d.parser._meta.firstSet;
8197
+ const fs = d.parser._def.tag === "lazy" ? firstSetOf(d.parser, /* @__PURE__ */ new Set(), this.refResolver()) : d.parser._meta.firstSet;
8184
8198
  if (fs.kind === "any" || matchesEmpty(d.parser)) return inner;
8185
8199
  const cls = this.charClass(fs);
8186
8200
  if (cls < 0) return inner;
@@ -16972,6 +16986,32 @@ var Serializer = class {
16972
16986
  body(c) {
16973
16987
  return c._def.tag === "lazy" && ruleNameOf4(c) !== void 0 ? lazyTarget(c) ?? c : c;
16974
16988
  }
16989
+ /**
16990
+ * The child a rule-entry grammar scope should serialize as its body, when that
16991
+ * scope merely WRAPS the rule's own named `lazy` proxy.
16992
+ *
16993
+ * `rules({ trackLines: true }, …)` wraps every rule VALUE — which is that rule's
16994
+ * own named lazy proxy — in a `grammarParser({ trackLines: true })` scope
16995
+ * (`parser.ts:229-242`). `body()` unwraps a BARE lazy entry to its defined target;
16996
+ * a lazy wrapped one level deep by that scope must be unwrapped INSIDE the scope
16997
+ * too. Left wrapped, the inner proxy serializes as a `g[name]` SELF-reference and
16998
+ * the rule's real body is dropped, so it re-lowers to `Name: parser(opts, g.Name)`
16999
+ * — a bodyless self-cycle the table encoder rejects with `alias cycle — lazy (rule
17000
+ * 'Name') encodes to its own recursion trampoline`. This is why the four
17001
+ * `*PositionsGrammar` exports fell back to a runtime `compose()` under trackLines.
17002
+ *
17003
+ * STRICT: only a scope that IS a rule entry (in `localRuleNames`) AND wraps ITS
17004
+ * OWN name unwraps. A scope wrapping another rule's ref (a genuine cross-rule
17005
+ * alias, `Name: parser(opts, g.Other)`) or a real body is untouched, so the
17006
+ * non-trackLines and non-self-wrap output stays byte-identical.
17007
+ */
17008
+ scopeSelfBody(c) {
17009
+ const name = this.localRuleNames.get(c);
17010
+ if (name === void 0 || c._def.tag !== "grammar") return void 0;
17011
+ const inner = c._def.parser;
17012
+ if (inner._def.tag !== "lazy" || ruleNameOf4(inner) !== name) return void 0;
17013
+ return lazyTarget(inner) ?? void 0;
17014
+ }
16975
17015
  run() {
16976
17016
  for (const [, c] of this.ruleMap) this.analyze(this.body(c), /* @__PURE__ */ new Set());
16977
17017
  for (const c of this.scanSkip) {
@@ -17041,7 +17081,9 @@ ${outerDecls.map((d) => " " + d).join("\n")}
17041
17081
  return;
17042
17082
  }
17043
17083
  active.add(c);
17044
- for (const child of childrenOf3(def)) this.analyze(child, active);
17084
+ const selfBody = def.tag === "grammar" ? this.scopeSelfBody(c) : void 0;
17085
+ const kids = selfBody === void 0 ? childrenOf3(def) : def.tag === "grammar" && def.triviaParser ? [selfBody, def.triviaParser] : [selfBody];
17086
+ for (const child of kids) this.analyze(child, active);
17045
17087
  active.delete(c);
17046
17088
  }
17047
17089
  /** Reference `c`: a const var if it has one (emitting its decl first), else inline.
@@ -17207,7 +17249,7 @@ ${outerDecls.map((d) => " " + d).join("\n")}
17207
17249
  const spec = classified && def.triviaParser ? analyzeLabeledTrivia(def.triviaParser) : null;
17208
17250
  if (classified && !spec) throw new Unserializable("classified trivia without labeled arms");
17209
17251
  const trivia2 = def.clearTrivia ? "null" : spec ? `classifiedTrivia({ ${spec.arms.map((arm) => `${JSON.stringify(arm.label)}: ${kid(arm.parser)}`).join(", ")} })` : def.triviaParser ? kid(def.triviaParser) : "undefined";
17210
- return `parser({ trivia: ${trivia2}${def.captureTrivia ? ", captureTrivia: true" : ""}${def.rootCapture ? `, rootCapture: ${JSON.stringify(def.rootCapture)}` : ""}${def.trackLines ? ", trackLines: true" : ""} }, ${kid(def.parser)})`;
17252
+ return `parser({ trivia: ${trivia2}${def.captureTrivia ? ", captureTrivia: true" : ""}${def.rootCapture ? `, rootCapture: ${JSON.stringify(def.rootCapture)}` : ""}${def.trackLines ? ", trackLines: true" : ""} }, ${kid(this.scopeSelfBody(c) ?? def.parser)})`;
17211
17253
  }
17212
17254
  case "withCtx": {
17213
17255
  if (def.extraSrc === void 0) throw new Unserializable("withCtx without extraSrc");
@@ -17343,7 +17385,7 @@ function compileRuleMap(ruleMap, opts = {}) {
17343
17385
  }
17344
17386
 
17345
17387
  // src/version.ts
17346
- var PARSEMAN_VERSION = "0.50.3";
17388
+ var PARSEMAN_VERSION = "0.50.5";
17347
17389
 
17348
17390
  // src/compiler/compile-linkable-table.ts
17349
17391
  function isLocal(rule) {