parseman 0.50.0 → 0.50.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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.0";
16
+ export declare const PARSEMAN_VERSION = "0.50.1";
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.0",
3
+ "version": "0.50.1",
4
4
  "packageManager": "pnpm@10.15.1",
5
5
  "engines": {
6
6
  "node": "^20.19.0 || >=22.13.0"
@@ -145,6 +145,22 @@ class Encoder {
145
145
  private choiceRollbackMasks = new Map<number, number>()
146
146
  private nonCommittingChoiceSites = new Set<number>()
147
147
  private failureRollbackCleanSites = new Set<number>()
148
+ /**
149
+ * `hasSideWriter` memo for `failureNeedsRollback`, shared across every call
150
+ * for the lifetime of this `Encoder`. A cached entry is written only when
151
+ * `complete` is true, which means the answer is a path-independent fact
152
+ * about that combinator node — it does not depend on which root's traversal
153
+ * asked, only on the node's own reachable subgraph. Scoping the memo per call
154
+ * (as a local inside `failureNeedsRollback`) was correct but discarded that
155
+ * fact after every arm, so a grammar with many ordered-choice arms / optional
156
+ * / attempt sites re-walked the SAME shared rule graph from scratch for each
157
+ * site — O(sites × graph size) instead of O(graph size). Measured on the
158
+ * scss-parser grammar (composed CSS-base superset, the deepest/largest of the
159
+ * four grammars): 47-50s for one `compileRuleMap` with the per-call memo,
160
+ * versus the whole macro transform completing in under a second once this
161
+ * memo is hoisted here.
162
+ */
163
+ private sideWriterMemo = new Map<Combinator<unknown>, boolean>()
148
164
  labels: readonly string[] | undefined = undefined
149
165
  /** Trivia table in scope at the row being encoded — codegen's `ctx.activeTrivia`. */
150
166
  activeTrivia: Combinator<unknown> | undefined = undefined
@@ -633,16 +649,19 @@ class Encoder {
633
649
  const refName = (p as unknown as { _ruleName?: string })._ruleName
634
650
  const winner = refName === undefined ? undefined : this.winners?.[refName]
635
651
  const root = winner !== undefined && !winnerWrapsReference(winner, p) ? winner : p
636
- const sideWriterMemo = new Map<Combinator<unknown>, boolean>()
637
- const hasSideWriter = (
638
- current: Combinator<unknown>,
639
- seen = new Set<Combinator<unknown>>(),
640
- ): { found: boolean; complete: boolean } => {
652
+ const sideWriterMemo = this.sideWriterMemo
653
+ // A single mutable `seen` set threaded through the whole call, rather than
654
+ // copied at every recursion level: this DFS visits children strictly
655
+ // sequentially, so marking `current` in progress before recursing and
656
+ // clearing it once its subtree is fully explored reproduces the exact same
657
+ // ancestor-path membership an immutable copy-per-level would have computed,
658
+ // without the O(depth) allocation on every node.
659
+ const seen = new Set<Combinator<unknown>>()
660
+ const hasSideWriter = (current: Combinator<unknown>): { found: boolean; complete: boolean } => {
641
661
  const cached = sideWriterMemo.get(current)
642
662
  if (cached !== undefined) return { found: cached, complete: true }
643
663
  if (seen.has(current)) return { found: false, complete: false }
644
- const next = new Set(seen)
645
- next.add(current)
664
+ seen.add(current)
646
665
  const def = current._def
647
666
  let result: { found: boolean; complete: boolean }
648
667
  switch (def.tag) {
@@ -657,10 +676,10 @@ class Encoder {
657
676
  const name = (current as unknown as { _ruleName?: string })._ruleName
658
677
  const resolvedWinner = name === undefined ? undefined : this.winners?.[name]
659
678
  if (resolvedWinner !== undefined && !winnerWrapsReference(resolvedWinner, current)) {
660
- result = hasSideWriter(resolvedWinner, next)
679
+ result = hasSideWriter(resolvedWinner)
661
680
  break
662
681
  }
663
- try { result = hasSideWriter(def.thunk(), next) }
682
+ try { result = hasSideWriter(def.thunk()) }
664
683
  catch { result = { found: true, complete: true } }
665
684
  break
666
685
  }
@@ -668,7 +687,7 @@ class Encoder {
668
687
  let complete = true
669
688
  result = { found: false, complete: true }
670
689
  for (const child of childrenOf(def)) {
671
- const childResult = hasSideWriter(child, next)
690
+ const childResult = hasSideWriter(child)
672
691
  if (childResult.found) {
673
692
  result = { found: true, complete: true }
674
693
  break
@@ -678,6 +697,7 @@ class Encoder {
678
697
  if (!result.found) result = { found: false, complete }
679
698
  }
680
699
  }
700
+ seen.delete(current)
681
701
  if (result.found || result.complete) sideWriterMemo.set(current, result.found)
682
702
  return result
683
703
  }
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.0'
16
+ export const PARSEMAN_VERSION = '0.50.1'