parseman 0.50.0 → 0.50.3
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/README.md +14 -10
- package/dist/analysis/diagnostics.js +455 -226
- package/dist/analysis/diagnostics.js.map +3 -3
- package/dist/cli/index.js +445 -191
- package/dist/cli/index.js.map +3 -3
- package/dist/combinators/choice.d.ts.map +1 -1
- package/dist/combinators/grammar.d.ts.map +1 -1
- package/dist/combinators/keywords.d.ts.map +1 -1
- package/dist/combinators/literal.d.ts.map +1 -1
- package/dist/combinators/map.d.ts.map +1 -1
- package/dist/combinators/ref.d.ts.map +1 -1
- package/dist/combinators/regex.d.ts.map +1 -1
- package/dist/combinators/repeat.d.ts.map +1 -1
- package/dist/combinators/scalar.d.ts +6 -0
- package/dist/combinators/scalar.d.ts.map +1 -0
- package/dist/combinators/sequence.d.ts.map +1 -1
- package/dist/combinators/trivia-skip.d.ts +2 -0
- package/dist/combinators/trivia-skip.d.ts.map +1 -1
- package/dist/compiler/token-capability.js +0 -3
- package/dist/compiler/token-capability.js.map +2 -2
- package/dist/index.js +446 -192
- package/dist/index.js.map +3 -3
- package/dist/language-service/index.js +68 -172
- package/dist/language-service/index.js.map +3 -3
- package/dist/parse-context.d.ts.map +1 -1
- package/dist/plugin/index.js +491 -306
- package/dist/plugin/index.js.map +3 -3
- package/dist/run/index.js +1 -0
- package/dist/run/index.js.map +1 -1
- package/dist/spec/index.js +399 -277
- package/dist/spec/index.js.map +3 -3
- package/dist/table/emit-assembly.d.ts.map +1 -1
- package/dist/table/encode.d.ts.map +1 -1
- package/dist/table/index.js +331 -188
- package/dist/table/index.js.map +3 -3
- package/dist/types.d.ts +8 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +4 -2
- package/src/combinators/choice.ts +34 -1
- package/src/combinators/grammar.ts +39 -0
- package/src/combinators/keywords.ts +14 -3
- package/src/combinators/literal.ts +26 -0
- package/src/combinators/map.ts +60 -0
- package/src/combinators/ref.ts +26 -7
- package/src/combinators/regex.ts +23 -0
- package/src/combinators/repeat.ts +109 -16
- package/src/combinators/scalar.ts +30 -0
- package/src/combinators/sequence.ts +76 -161
- package/src/combinators/trivia-skip.ts +14 -1
- package/src/parse-context.ts +1 -0
- package/src/table/emit-assembly.ts +39 -16
- package/src/table/encode.ts +30 -10
- package/src/types.ts +8 -0
- package/src/version.ts +1 -1
|
@@ -259,6 +259,16 @@ function q(s: string): string {
|
|
|
259
259
|
return JSON.stringify(s)
|
|
260
260
|
}
|
|
261
261
|
|
|
262
|
+
/**
|
|
263
|
+
* A code-unit lookup is a complete first-code-point decision only for a class
|
|
264
|
+
* with no non-ASCII range. At EOF `charCodeAt` yields NaN; lone surrogates and
|
|
265
|
+
* the leading surrogate of an astral point are all outside the 128-slot array,
|
|
266
|
+
* so each reads `undefined` and declines exactly as `classHas(lead(...))` does.
|
|
267
|
+
*/
|
|
268
|
+
function isAsciiOnlyClass(cls: ResolvedClass | null | undefined): cls is ResolvedClass {
|
|
269
|
+
return cls !== null && cls !== undefined && cls.hi.length === 0
|
|
270
|
+
}
|
|
271
|
+
|
|
262
272
|
/**
|
|
263
273
|
* WHICH SIDE SINKS A MARK IN THIS ASSEMBLY HAS TO COVER.
|
|
264
274
|
*
|
|
@@ -1259,12 +1269,15 @@ ${cfg.probe ? `failAt(ctx,${xf},pos)\n` : ''}return FAIL
|
|
|
1259
1269
|
|
|
1260
1270
|
case OP_GATE: {
|
|
1261
1271
|
const child = link(code[ip + 2]!)
|
|
1262
|
-
const
|
|
1272
|
+
const resolved = t.cc[code[ip + 1]!]!
|
|
1273
|
+
const ci = classes.push([resolved]) - 1
|
|
1263
1274
|
classPlan.push([code[ip + 1]!])
|
|
1264
1275
|
const cls = hoist('cl', `CLS[${ci}][0]`)
|
|
1265
1276
|
const xf = fxRef(code[ip + 3]!)
|
|
1266
1277
|
return `${head}
|
|
1267
|
-
if(
|
|
1278
|
+
if(${staticBuild && isAsciiOnlyClass(resolved)
|
|
1279
|
+
? `${cls}.ascii[input.charCodeAt(pos)]!==1`
|
|
1280
|
+
: `!classHas(${cls},lead(input,pos))`}){ctx._fe=pos;ctx._fx=${xf};return FAIL}
|
|
1268
1281
|
return ${child}(input,pos,ctx)
|
|
1269
1282
|
}`
|
|
1270
1283
|
}
|
|
@@ -1700,10 +1713,11 @@ return v
|
|
|
1700
1713
|
// array index feeding ONE call site, which is the megamorphic site
|
|
1701
1714
|
// this unit exists to remove.
|
|
1702
1715
|
const armSwitch = arms.map((a, i) => `case ${i}:v=${a}(input,pos,ctx);break`).join('\n')
|
|
1716
|
+
const directAscii = staticBuild && table.hi.length === 0
|
|
1703
1717
|
return `${head}
|
|
1704
|
-
const c
|
|
1718
|
+
const c=${directAscii ? 'input.charCodeAt(pos)' : 'lead(input,pos)'}
|
|
1705
1719
|
let arm=-1
|
|
1706
|
-
if(c>=0&&c<128){const a=${asc}[c];if(a!==0)arm=a-1}
|
|
1720
|
+
if(${directAscii ? 'c<128' : 'c>=0&&c<128'}){const a=${asc}[c];if(a!==0)arm=a-1}
|
|
1707
1721
|
${hiArr.length === 0 ? '' : `else if(c>=128){const h=${hoist('hi', `DISP[${di}].hi`)}
|
|
1708
1722
|
for(let i=0;i<h.length;i+=3){if(c>=h[i]&&c<=h[i+1]){arm=h[i+2];break}}}`}
|
|
1709
1723
|
if(arm>=0){
|
|
@@ -1729,7 +1743,11 @@ return FAIL
|
|
|
1729
1743
|
const di = code[ip + 1]!
|
|
1730
1744
|
const expected = Array.from({ length: n }, (_, i) => fxRef(code[base + n + i]!))
|
|
1731
1745
|
const gates = Array.from({ length: n }, (_, i) => table.armCls[i] ?? null)
|
|
1732
|
-
const
|
|
1746
|
+
const maskable = n <= 32
|
|
1747
|
+
const directAsciiMask = staticBuild && maskable && gates.every(isAsciiOnlyClass)
|
|
1748
|
+
const gateRefs = directAsciiMask
|
|
1749
|
+
? []
|
|
1750
|
+
: Array.from({ length: n }, (_, i) => hoist('g', `DISP[${di}].armCls[${i}]`))
|
|
1733
1751
|
type ChoicePretest = {
|
|
1734
1752
|
readonly scalar?: readonly string[]
|
|
1735
1753
|
readonly sequence?: ScalarSequenceRef
|
|
@@ -1754,7 +1772,6 @@ return FAIL
|
|
|
1754
1772
|
const sequence = largeChoiceScalarSequences.get(code[base + i]!)
|
|
1755
1773
|
return sequence === undefined ? undefined : { sequence: scalarSequenceRef(sequence) }
|
|
1756
1774
|
})
|
|
1757
|
-
const maskable = n <= 32
|
|
1758
1775
|
const maskName = maskable
|
|
1759
1776
|
? hoist('mk', `MASK[${masks.push(maskForClassRow(gates)) - 1}]`)
|
|
1760
1777
|
: ''
|
|
@@ -1856,16 +1873,16 @@ ${merge}
|
|
|
1856
1873
|
ctx._fe=pos;ctx._fx=acc??${choiceFx}
|
|
1857
1874
|
return FAIL`
|
|
1858
1875
|
return `${head}
|
|
1859
|
-
const c
|
|
1876
|
+
const c=${directAsciiMask ? 'input.charCodeAt(pos)' : 'lead(input,pos)'}
|
|
1860
1877
|
${hasRollback ? emitMark(p, L.buf, L.raw, sinks) : ''}
|
|
1861
1878
|
${deepAt.map((at, i) => `let ${at},${deepFx[i]}`).join('\n')}
|
|
1862
1879
|
${maskable ? `if(c<128){
|
|
1863
|
-
const bits=${maskName}[c<0?128:c]
|
|
1880
|
+
const bits=${maskName}[${directAsciiMask ? 'c' : 'c<0?128:c'}]
|
|
1864
1881
|
if(bits===0){ctx._fe=pos;ctx._fx=${choiceFx};return FAIL}
|
|
1865
1882
|
${lazyArms(true)}
|
|
1866
|
-
}else{
|
|
1883
|
+
}${directAsciiMask ? '' : `else{
|
|
1867
1884
|
${lazyArms(false)}
|
|
1868
|
-
}
|
|
1885
|
+
}`}
|
|
1869
1886
|
` : lazyArms(false)}
|
|
1870
1887
|
${finish}
|
|
1871
1888
|
}`
|
|
@@ -1920,7 +1937,7 @@ if(ctx._fc===true){${startFailureExact ? `if(best===pos){acc=${catchName}(${i},0
|
|
|
1920
1937
|
${rollbackFor(i)}
|
|
1921
1938
|
}${routeMiss}${sequenceMiss}`
|
|
1922
1939
|
}).join('\n') : ''
|
|
1923
|
-
const generalArms = arms.map((arm, i) => {
|
|
1940
|
+
const generalArms = directAsciiMask ? '' : arms.map((arm, i) => {
|
|
1924
1941
|
const pretest = pretests[i]
|
|
1925
1942
|
const decision = pretest?.token === undefined && pretest?.sequence === undefined ? '' : tmp()
|
|
1926
1943
|
const scalarCondition = pretest?.scalar === undefined ? ''
|
|
@@ -1963,12 +1980,12 @@ ${rollbackFor(i)}
|
|
|
1963
1980
|
// arm can run, so the choice's encoded flat expected set is already
|
|
1964
1981
|
// the exact result. Do not walk the arm ladder merely to rebuild it.
|
|
1965
1982
|
return `${head}
|
|
1966
|
-
const c
|
|
1983
|
+
const c=${directAsciiMask ? 'input.charCodeAt(pos)' : 'lead(input,pos)'}
|
|
1967
1984
|
${hasRollback ? emitMark(p, L.buf, L.raw, sinks) : ''}
|
|
1968
1985
|
let acc
|
|
1969
1986
|
let best=pos
|
|
1970
1987
|
${maskable ? `if(c<128){
|
|
1971
|
-
const bits=${maskName}[c<0?128:c]
|
|
1988
|
+
const bits=${maskName}[${directAsciiMask ? 'c' : 'c<0?128:c'}]
|
|
1972
1989
|
if(bits===0){ctx._fe=pos;ctx._fx=${choiceFx};return FAIL}
|
|
1973
1990
|
${startFailureExact ? '' : 'let prev=0'}
|
|
1974
1991
|
${maskArms}
|
|
@@ -2076,14 +2093,20 @@ return [key,v]
|
|
|
2076
2093
|
const itemFx = reportItem ? fxRef(code[ip + 6]!) : 'EMPTY_FX'
|
|
2077
2094
|
const collect = op === OP_REP
|
|
2078
2095
|
let itemCls: string | undefined
|
|
2096
|
+
let itemAscii = false
|
|
2079
2097
|
if (!REC) {
|
|
2080
2098
|
const itemClassIndex = sepIp < 0 ? code[ip + 7]! : -1
|
|
2081
2099
|
if (itemClassIndex >= 0) {
|
|
2082
|
-
const
|
|
2100
|
+
const resolved = t.cc[itemClassIndex]!
|
|
2101
|
+
const ci = classes.push([resolved]) - 1
|
|
2083
2102
|
classPlan.push([itemClassIndex])
|
|
2084
2103
|
itemCls = hoist('ri', `CLS[${ci}][0]`)
|
|
2104
|
+
itemAscii = staticBuild && isAsciiOnlyClass(resolved)
|
|
2085
2105
|
}
|
|
2086
2106
|
}
|
|
2107
|
+
const itemMiss = (at: string): string => itemAscii
|
|
2108
|
+
? `${itemCls}.ascii[input.charCodeAt(${at})]!==1`
|
|
2109
|
+
: `!classHas(${itemCls},lead(input,${at}))`
|
|
2087
2110
|
const skipBeforeFirst = sepIp < 0 && min === 0
|
|
2088
2111
|
const p = tmp()
|
|
2089
2112
|
// `viaRepItem` is `sep === undefined && count >= min && (count > 0 ||
|
|
@@ -2199,7 +2222,7 @@ const out=${collect ? '[]' : 'undefined'}
|
|
|
2199
2222
|
${knownTrivia === undefined ? 'const hasTrivia=ctx.trivia!==undefined\n' : ''}${L.buf ? '' : 'const needMark=_rollbackNeeded(ctx)\n'}${REC ? `const ${my}=ctx._sync\n` : ''}${itemCls === undefined ? '' : `const ${p}gate=ctx._probe===undefined\n`}let cur=pos
|
|
2200
2223
|
let count=0
|
|
2201
2224
|
for(;;){
|
|
2202
|
-
${max >= 0 ? `if(count>=${max})break\n` : ''}${sep !== undefined ? `if(count>0&&count>=${min}&&cur>=input.length)break\n` : ''}${itemCls !== undefined && sep === undefined ? `if(count>=${min}&&${p}gate&&${hasTrivia === 'false' ? 'true' : hasTrivia === 'true' ? 'false' : '!hasTrivia'}
|
|
2225
|
+
${max >= 0 ? `if(count>=${max})break\n` : ''}${sep !== undefined ? `if(count>0&&count>=${min}&&cur>=input.length)break\n` : ''}${itemCls !== undefined && sep === undefined ? `if(count>=${min}&&${p}gate&&${hasTrivia === 'false' ? 'true' : hasTrivia === 'true' ? 'false' : '!hasTrivia'}&&${itemMiss('cur')})break\n` : ''}let ${p}raw=0,${p}tl=0,${p}lv=0,${p}lg=0,${p}rt=0${sinks.fd ? `,${p}fd=0` : ''}${sinks.er ? `,${p}er=0` : ''}
|
|
2203
2226
|
${markBody}
|
|
2204
2227
|
let itemStart=cur
|
|
2205
2228
|
let sepEnd=-1
|
|
@@ -2221,7 +2244,7 @@ itemStart=${hasTrivia === 'false' ? 'EC.e' : hasTrivia === 'true' ? `${skip}(inp
|
|
|
2221
2244
|
${rb}
|
|
2222
2245
|
${trailingAllowed ? 'if(sepEnd>=0)cur=sepEnd\n' : ''}break
|
|
2223
2246
|
}
|
|
2224
|
-
${itemCls === undefined ? '' : `if(count>=${min}&&${p}gate
|
|
2247
|
+
${itemCls === undefined ? '' : `if(count>=${min}&&${p}gate&&${itemMiss('itemStart')}){
|
|
2225
2248
|
${rb}
|
|
2226
2249
|
${trailingAllowed ? 'if(sepEnd>=0)cur=sepEnd\n' : ''}break
|
|
2227
2250
|
}
|
package/src/table/encode.ts
CHANGED
|
@@ -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 =
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
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
|
-
|
|
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
|
|
679
|
+
result = hasSideWriter(resolvedWinner)
|
|
661
680
|
break
|
|
662
681
|
}
|
|
663
|
-
try { result = hasSideWriter(def.thunk()
|
|
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
|
|
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/types.ts
CHANGED
|
@@ -115,6 +115,10 @@ export type Combinator<T> = {
|
|
|
115
115
|
readonly _tag: string
|
|
116
116
|
readonly _meta: ParserMeta
|
|
117
117
|
readonly _def: ParserDef
|
|
118
|
+
/** Experimental strict interpreter ABI: end >= 0 succeeds, ~failurePos fails. */
|
|
119
|
+
readonly _parseScalar?: ((input: string, pos: number, ctx: ParseContext) => number) | undefined
|
|
120
|
+
/** Construction-owned sticky matcher for exact terminal fusion. */
|
|
121
|
+
readonly _stickyRegex?: RegExp
|
|
118
122
|
parse(input: string, pos: number, ctx: ParseContext): ParseResult<T>
|
|
119
123
|
}
|
|
120
124
|
|
|
@@ -311,6 +315,8 @@ export type ParseContext = {
|
|
|
311
315
|
_fx?: string[] | undefined
|
|
312
316
|
/** Framework-internal compiled-output committed-failure flag. */
|
|
313
317
|
_fc?: boolean | undefined
|
|
318
|
+
/** Experimental strict interpreter semantic-value register. */
|
|
319
|
+
_sv?: unknown
|
|
314
320
|
/**
|
|
315
321
|
* When set by completionsAt(), tracks the highest-position ParseFail seen
|
|
316
322
|
* during parsing up to _probe.offset. Used to return completions at the cursor
|
|
@@ -427,6 +433,8 @@ export type ParserMeta = {
|
|
|
427
433
|
isTrivia: boolean
|
|
428
434
|
/** User-defined labels for labeled trivia arms (`label(name, parser)`). */
|
|
429
435
|
triviaKindLabels?: readonly string[]
|
|
436
|
+
/** Preclassified lightweight trivia scanner for the ordinary skip path. */
|
|
437
|
+
triviaScanner?: ((input: string, cur: number) => number) | null
|
|
430
438
|
/** Set only by `classifiedTrivia()`: each root-visible category is a separate
|
|
431
439
|
* grammar arm rather than an arbitrary label on a broad recognizer. */
|
|
432
440
|
rootTriviaClassified?: true
|
package/src/version.ts
CHANGED