parseman 0.50.4 → 0.50.6

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.6";
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.6",
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
@@ -756,6 +756,19 @@ function exprToCombi(node: Expression, scope: XScope, code?: string, mfs?: strin
756
756
  if (prov) carriedImports.push({ local: name, source: prov.source, imported: prov.imported })
757
757
  else unresolved.push(name)
758
758
  }
759
+ } else if (be!.type === 'Identifier' && typeof be!.name === 'string' && combi._def.buildSrc === be!.name) {
760
+ // A NAMED reducer that is a BARE IMPORTED identifier — `node(t, parser, mkRel)`
761
+ // where `mkRel` is `import { mkRel } from './astf'`. The table inlines the
762
+ // reducer as its NAME (`f:[mkRel]`), and a cross-package compose that re-lowers
763
+ // this carried piece re-emits that name verbatim into the consuming module —
764
+ // which does not import it, so the fused artifact throws ReferenceError. Carry
765
+ // the ONE import the emitted name needs so the downstream re-emit binds it. This
766
+ // is the reducer NAME's own import (unambiguous — it is THIS module's import),
767
+ // never the reducer BODY's internal free names (the `buildSigSrc` P1 trap): the
768
+ // guard `buildSrc === be.name` also excludes a foreign-factory reducer whose
769
+ // source was rewritten to a self-contained body above.
770
+ const prov = _builderImports?.(be!.name) ?? null
771
+ if (prov) carriedImports.push({ local: be!.name, source: prov.source, imported: prov.imported })
759
772
  }
760
773
  const staticError = [...report.structural, ...unresolved]
761
774
  if (staticError.length > 0) combi._def.buildStaticError = staticError
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.6'