parseman 0.50.4 → 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/version.d.ts CHANGED
@@ -13,5 +13,5 @@
13
13
  * Kept in sync with `package.json` by `test/unit/version-sync.test.ts`, which fails
14
14
  * the build if this constant drifts from the published version.
15
15
  */
16
- export declare const PARSEMAN_VERSION = "0.50.4";
16
+ export declare const PARSEMAN_VERSION = "0.50.5";
17
17
  //# sourceMappingURL=version.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "parseman",
3
- "version": "0.50.4",
3
+ "version": "0.50.5",
4
4
  "packageManager": "pnpm@10.15.1",
5
5
  "engines": {
6
6
  "node": "^20.19.0 || >=22.13.0"
@@ -321,6 +321,33 @@ class Serializer {
321
321
  return c._def.tag === 'lazy' && ruleNameOf(c) !== undefined ? (lazyTarget(c) ?? c) : c
322
322
  }
323
323
 
324
+ /**
325
+ * The child a rule-entry grammar scope should serialize as its body, when that
326
+ * scope merely WRAPS the rule's own named `lazy` proxy.
327
+ *
328
+ * `rules({ trackLines: true }, …)` wraps every rule VALUE — which is that rule's
329
+ * own named lazy proxy — in a `grammarParser({ trackLines: true })` scope
330
+ * (`parser.ts:229-242`). `body()` unwraps a BARE lazy entry to its defined target;
331
+ * a lazy wrapped one level deep by that scope must be unwrapped INSIDE the scope
332
+ * too. Left wrapped, the inner proxy serializes as a `g[name]` SELF-reference and
333
+ * the rule's real body is dropped, so it re-lowers to `Name: parser(opts, g.Name)`
334
+ * — a bodyless self-cycle the table encoder rejects with `alias cycle — lazy (rule
335
+ * 'Name') encodes to its own recursion trampoline`. This is why the four
336
+ * `*PositionsGrammar` exports fell back to a runtime `compose()` under trackLines.
337
+ *
338
+ * STRICT: only a scope that IS a rule entry (in `localRuleNames`) AND wraps ITS
339
+ * OWN name unwraps. A scope wrapping another rule's ref (a genuine cross-rule
340
+ * alias, `Name: parser(opts, g.Other)`) or a real body is untouched, so the
341
+ * non-trackLines and non-self-wrap output stays byte-identical.
342
+ */
343
+ private scopeSelfBody(c: Comb): Comb | undefined {
344
+ const name = this.localRuleNames.get(c)
345
+ if (name === undefined || c._def.tag !== 'grammar') return undefined
346
+ const inner = c._def.parser
347
+ if (inner._def.tag !== 'lazy' || ruleNameOf(inner) !== name) return undefined
348
+ return lazyTarget(inner) ?? undefined
349
+ }
350
+
324
351
  run(): string {
325
352
  for (const [, c] of this.ruleMap) this.analyze(this.body(c), new Set())
326
353
  for (const c of this.scanSkip) { this.assertNoRuleRef(c, new Set()); this.analyze(c, new Set()) }
@@ -415,7 +442,19 @@ class Serializer {
415
442
  return
416
443
  }
417
444
  active.add(c)
418
- for (const child of childrenOf(def)) this.analyze(child, active)
445
+ // When a rule-entry scope wraps its own named lazy, substitute the lazy's
446
+ // unwrapped body for `def.parser` — but keep the scope's OTHER children (its
447
+ // `triviaParser`) in the analysis, exactly as `childrenOf('grammar')` returns
448
+ // both. Dropping the trivia parser here would leave a recursive/shared trivia
449
+ // unanalysed, so its const/self-ref hoisting would be missing when `expr` emits
450
+ // it via `kid(def.triviaParser)`. selfBody is non-undefined only for `'grammar'`.
451
+ const selfBody = def.tag === 'grammar' ? this.scopeSelfBody(c) : undefined
452
+ const kids = selfBody === undefined
453
+ ? childrenOf(def)
454
+ : def.tag === 'grammar' && def.triviaParser
455
+ ? [selfBody, def.triviaParser]
456
+ : [selfBody]
457
+ for (const child of kids) this.analyze(child, active)
419
458
  active.delete(c)
420
459
  }
421
460
 
@@ -602,7 +641,7 @@ class Serializer {
602
641
  : spec
603
642
  ? `classifiedTrivia({ ${spec.arms.map(arm => `${JSON.stringify(arm.label)}: ${kid(arm.parser)}`).join(', ')} })`
604
643
  : def.triviaParser ? kid(def.triviaParser) : 'undefined'
605
- return `parser({ trivia: ${trivia}${def.captureTrivia ? ', captureTrivia: true' : ''}${def.rootCapture ? `, rootCapture: ${JSON.stringify(def.rootCapture)}` : ''}${def.trackLines ? ', trackLines: true' : ''} }, ${kid(def.parser)})`
644
+ return `parser({ trivia: ${trivia}${def.captureTrivia ? ', captureTrivia: true' : ''}${def.rootCapture ? `, rootCapture: ${JSON.stringify(def.rootCapture)}` : ''}${def.trackLines ? ', trackLines: true' : ''} }, ${kid(this.scopeSelfBody(c) ?? def.parser)})`
606
645
  }
607
646
  case 'withCtx': {
608
647
  // A `withCtx` round-trips through `_wc`, which rebuilds it AND re-attaches
package/src/version.ts CHANGED
@@ -13,4 +13,4 @@
13
13
  * Kept in sync with `package.json` by `test/unit/version-sync.test.ts`, which fails
14
14
  * the build if this constant drifts from the published version.
15
15
  */
16
- export const PARSEMAN_VERSION = '0.50.4'
16
+ export const PARSEMAN_VERSION = '0.50.5'