parseman 0.50.3 → 0.50.4
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/diagnostics.js +24 -10
- package/dist/analysis/diagnostics.js.map +2 -2
- package/dist/cli/index.js +24 -10
- package/dist/cli/index.js.map +2 -2
- package/dist/combinators/first-set.d.ts.map +1 -1
- package/dist/compiler/token-capability.js +22 -8
- package/dist/compiler/token-capability.js.map +1 -1
- package/dist/index.js +25 -11
- package/dist/index.js.map +1 -1
- package/dist/plugin/index.js +25 -11
- package/dist/plugin/index.js.map +1 -1
- package/dist/spec/index.js +22 -8
- package/dist/spec/index.js.map +1 -1
- package/dist/table/encode.d.ts.map +1 -1
- package/dist/table/index.js +24 -10
- package/dist/table/index.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +2 -2
- package/src/combinators/first-set.ts +33 -7
- package/src/table/encode.ts +14 -2
- package/src/version.ts +1 -1
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.
|
|
16
|
+
export declare const PARSEMAN_VERSION = "0.50.4";
|
|
17
17
|
//# sourceMappingURL=version.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "parseman",
|
|
3
|
-
"version": "0.50.
|
|
3
|
+
"version": "0.50.4",
|
|
4
4
|
"packageManager": "pnpm@10.15.1",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": "^20.19.0 || >=22.13.0"
|
|
7
7
|
},
|
|
8
|
-
"description": "Parser combinators that compile to optimized JavaScript
|
|
8
|
+
"description": "Parser combinators that compile to optimized JavaScript — use as a library or as a build-time macro",
|
|
9
9
|
"type": "module",
|
|
10
10
|
"sideEffects": false,
|
|
11
11
|
"author": "Matthew Dean <matthew-dean@users.noreply.github.com>",
|
|
@@ -63,11 +63,31 @@ export function classifyFinalChoice(
|
|
|
63
63
|
return { exclusive, firstSets, nullable }
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
/** The
|
|
67
|
-
|
|
66
|
+
/** The compose WINNER a named lazy ref resolves to — the same row the encoder's
|
|
67
|
+
* `lazy` case calls via `winners`/`scopedRef`. Winner-FIRST resolution is what makes
|
|
68
|
+
* first-set / nullability analysis AGREE with emission: a base piece's thunk still
|
|
69
|
+
* closes over its own binding, so a compose OVERRIDE (and any undefined cross-piece
|
|
70
|
+
* ref) would otherwise be invisible to the gate — the reducer reroutes, the recognizer
|
|
71
|
+
* does not. `undefined` → no winner; fall back to the thunk. Monolithic: the winner IS
|
|
72
|
+
* the thunk's target, a no-op. A winner that recurses back to `p` (recursive or
|
|
73
|
+
* self-alias rule) is caught by the shared `seen` guard → `any`, a sound over-approx. */
|
|
74
|
+
function winnerForRef(p: Combinator<unknown>, resolve: RefResolver | undefined): Combinator<unknown> | undefined {
|
|
68
75
|
if (resolve === undefined) return undefined
|
|
69
76
|
const name = (p as unknown as { _ruleName?: string })._ruleName
|
|
70
|
-
|
|
77
|
+
const w = name === undefined ? undefined : resolve(name)
|
|
78
|
+
// Skip a winner that merely wraps THIS reference (a transparent rules()/parser()
|
|
79
|
+
// alias): resolving to it is a self-hop, not an override — matches the encoder's
|
|
80
|
+
// own `winnerWrapsReference` guard so first-set analysis agrees with emission.
|
|
81
|
+
if (w === undefined || w === p) return undefined
|
|
82
|
+
let cur = w
|
|
83
|
+
for (const seen = new Set<Combinator<unknown>>(); !seen.has(cur); ) {
|
|
84
|
+
if (cur === p) return undefined
|
|
85
|
+
seen.add(cur)
|
|
86
|
+
const cd = cur._def as ParserDef
|
|
87
|
+
if (cd.tag !== 'grammar' && cd.tag !== 'trivia') return w
|
|
88
|
+
cur = cd.parser
|
|
89
|
+
}
|
|
90
|
+
return w
|
|
71
91
|
}
|
|
72
92
|
|
|
73
93
|
export function union(a: FirstSet, b: FirstSet): FirstSet {
|
|
@@ -190,9 +210,12 @@ function matchesEmptyBody(
|
|
|
190
210
|
case 'node':
|
|
191
211
|
case 'grammar':
|
|
192
212
|
case 'recover': return me(d.parser)
|
|
193
|
-
case 'lazy':
|
|
213
|
+
case 'lazy': {
|
|
214
|
+
const w = winnerForRef(p, resolve)
|
|
215
|
+
if (w !== undefined) return me(w)
|
|
194
216
|
try { return me(d.thunk()) }
|
|
195
|
-
catch {
|
|
217
|
+
catch { return true }
|
|
218
|
+
}
|
|
196
219
|
default: return true // scanTo / guard / unknown → assume nullable (safe)
|
|
197
220
|
}
|
|
198
221
|
}
|
|
@@ -338,9 +361,12 @@ function firstSetBody(
|
|
|
338
361
|
case 'regex':
|
|
339
362
|
case 'keywords': return p._meta.firstSet // terminals: no refs, cached set is exact
|
|
340
363
|
case 'dispatch': return fs(d.selector)
|
|
341
|
-
case 'lazy':
|
|
364
|
+
case 'lazy': {
|
|
365
|
+
const w = winnerForRef(p, resolve)
|
|
366
|
+
if (w !== undefined) return fs(w)
|
|
342
367
|
try { return fs(d.thunk()) }
|
|
343
|
-
catch {
|
|
368
|
+
catch { return any() }
|
|
369
|
+
}
|
|
344
370
|
case 'choice': {
|
|
345
371
|
let out: FirstSet = empty()
|
|
346
372
|
for (const arm of d.parsers) out = union(out, fs(arm))
|
package/src/table/encode.ts
CHANGED
|
@@ -1404,7 +1404,15 @@ class Encoder {
|
|
|
1404
1404
|
// names tokens the run would have skipped — `['"@"', '"(", '"x"']` where
|
|
1405
1405
|
// running the body reports `['"x"']` alone.
|
|
1406
1406
|
if (matchesEmpty(d.parser)) return body
|
|
1407
|
-
|
|
1407
|
+
// A node whose BODY is a bare cross-rule ref (`node('X', g.Y, …)`) caches its
|
|
1408
|
+
// first set from the ref's base binding. Under a compose OVERRIDE the winner
|
|
1409
|
+
// for `Y` may be WIDER, so the cached set gates out input the merged rule
|
|
1410
|
+
// accepts (the reducer reroutes via `winners`, but this recognizer gate did
|
|
1411
|
+
// not). Resolve the ref's gate through `winners`, exactly as the `lazy` case
|
|
1412
|
+
// resolves the call. Non-ref bodies keep the cached set (byte-identical).
|
|
1413
|
+
const fs = d.parser._def.tag === 'lazy'
|
|
1414
|
+
? firstSetOf(d.parser, new Set(), this.refResolver())
|
|
1415
|
+
: d.parser._meta.firstSet
|
|
1408
1416
|
if (fs.kind === 'any') return body
|
|
1409
1417
|
const cls = this.charClass(fs)
|
|
1410
1418
|
if (cls < 0) return body
|
|
@@ -1594,7 +1602,11 @@ class Encoder {
|
|
|
1594
1602
|
// run itself would have skipped past. Running the inner instead reported a
|
|
1595
1603
|
// strictly smaller set, so omitting the guard was an observable divergence,
|
|
1596
1604
|
// not a slower equivalent.
|
|
1597
|
-
|
|
1605
|
+
// Same override-aware resolution as the node gate: a bare-ref inner resolves
|
|
1606
|
+
// its gate through `winners` so a widened winner is not gated out.
|
|
1607
|
+
const fs = d.parser._def.tag === 'lazy'
|
|
1608
|
+
? firstSetOf(d.parser, new Set(), this.refResolver())
|
|
1609
|
+
: d.parser._meta.firstSet
|
|
1598
1610
|
if (fs.kind === 'any' || matchesEmpty(d.parser)) return inner
|
|
1599
1611
|
const cls = this.charClass(fs)
|
|
1600
1612
|
if (cls < 0) return inner
|
package/src/version.ts
CHANGED