parseman 0.58.2 → 0.58.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/dist/analysis/diagnostics.js +203 -37
- package/dist/analysis/diagnostics.js.map +3 -3
- package/dist/cli/index.js +203 -37
- package/dist/cli/index.js.map +3 -3
- package/dist/combinators/first-set.d.ts.map +1 -1
- package/dist/combinators/regex.d.ts.map +1 -1
- package/dist/compiler/token-capability.js +28 -4
- package/dist/compiler/token-capability.js.map +2 -2
- package/dist/compiler/token-scanner.d.ts.map +1 -1
- package/dist/index.js +207 -41
- package/dist/index.js.map +3 -3
- package/dist/language-service/index.js +10 -3
- package/dist/language-service/index.js.map +2 -2
- package/dist/lead-ranges.d.ts +26 -0
- package/dist/lead-ranges.d.ts.map +1 -0
- package/dist/plugin/index.js +207 -41
- package/dist/plugin/index.js.map +3 -3
- package/dist/recovery/scan.d.ts.map +1 -1
- package/dist/regex/classes.d.ts +5 -3
- package/dist/regex/classes.d.ts.map +1 -1
- package/dist/regex/first-set.d.ts +7 -1
- package/dist/regex/first-set.d.ts.map +1 -1
- package/dist/run/index.js +10 -3
- package/dist/run/index.js.map +2 -2
- package/dist/table/assemble.d.ts.map +1 -1
- package/dist/table/compile-rule-map.d.ts.map +1 -1
- package/dist/table/emit-assembly.d.ts +8 -1
- package/dist/table/emit-assembly.d.ts.map +1 -1
- package/dist/table/encode-baseline.d.ts.map +1 -1
- package/dist/table/index.js +206 -40
- package/dist/table/index.js.map +3 -3
- package/dist/table/program.d.ts.map +1 -1
- package/dist/table/scan-shapes.d.ts +1 -1
- package/dist/table/scan-shapes.d.ts.map +1 -1
- package/dist/table/site-labels.d.ts.map +1 -1
- package/dist/table/trivia-gap.d.ts +11 -1
- package/dist/table/trivia-gap.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
- package/src/combinators/choice.ts +4 -0
- package/src/combinators/first-set.ts +13 -4
- package/src/combinators/regex.ts +28 -9
- package/src/compiler/token-scanner.ts +4 -1
- package/src/lead-ranges.ts +53 -0
- package/src/recovery/scan.ts +2 -3
- package/src/regex/classes.ts +22 -7
- package/src/regex/first-set.ts +67 -12
- package/src/table/assemble.ts +42 -3
- package/src/table/compile-rule-map.ts +5 -3
- package/src/table/emit-assembly.ts +42 -8
- package/src/table/encode-baseline.ts +2 -1
- package/src/table/program.ts +2 -1
- package/src/table/scan-shapes.ts +5 -2
- package/src/table/site-labels.ts +2 -1
- package/src/table/trivia-gap.ts +19 -0
- package/src/version.ts +1 -1
package/src/table/assemble.ts
CHANGED
|
@@ -69,7 +69,7 @@ import type { Combinator, FieldMap, FirstSet, ParseContext, ParseResult, ParserD
|
|
|
69
69
|
import { balanced, scanTo } from '../combinators/scanTo.ts'
|
|
70
70
|
import { buildFieldMap } from '../compiler/fields.ts'
|
|
71
71
|
import { FIELD_LEADING_TRIVIA, FIELD_TRAILING_TRIVIA, LEAD, triviaFieldCapture } from '../combinators/field-trivia.ts'
|
|
72
|
-
import { skipDeferredTrivia } from './trivia-gap.ts'
|
|
72
|
+
import { skipDeferredTrivia, triviaLeadTable } from './trivia-gap.ts'
|
|
73
73
|
import { asciiFoldKey } from '../combinators/dispatch.ts'
|
|
74
74
|
import { projectChild, unwrapChild } from '../combinators/node.ts'
|
|
75
75
|
import { cstOutputHost } from '../compiler/build-arity.ts'
|
|
@@ -959,7 +959,31 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
959
959
|
return out
|
|
960
960
|
}
|
|
961
961
|
|
|
962
|
+
/**
|
|
963
|
+
* NO TRIVIA CAN START AT `cur` — `triviaLeadTable` for the installed trivia. A
|
|
964
|
+
* gap there is empty: nothing to scan, nothing recorded, so nothing for a
|
|
965
|
+
* sequence term to mark. One table per trivia slot, built with the assembly;
|
|
966
|
+
* the installed trivia is matched to its slot only when it changes (a trivia
|
|
967
|
+
* the caller passed that is no slot gets its own table then).
|
|
968
|
+
*/
|
|
969
|
+
const LEAD_TABLES = trivia.map(tp => tp === undefined ? null : triviaLeadTable(tp))
|
|
970
|
+
let LEAD_OF: Combinator<unknown> | undefined
|
|
971
|
+
let LEAD_TABLE: Uint8Array | null = null
|
|
972
|
+
function cannotStartTrivia(input: string, cur: number, triviaP: Combinator<unknown>): boolean {
|
|
973
|
+
if (triviaP !== LEAD_OF) {
|
|
974
|
+
LEAD_OF = triviaP
|
|
975
|
+
const slot = trivia.indexOf(triviaP)
|
|
976
|
+
LEAD_TABLE = slot >= 0 ? LEAD_TABLES[slot]! : triviaLeadTable(triviaP)
|
|
977
|
+
}
|
|
978
|
+
const table = LEAD_TABLE
|
|
979
|
+
if (table === null) return false
|
|
980
|
+
const c = input.charCodeAt(cur)
|
|
981
|
+
return c < 128 && table[c] === 0
|
|
982
|
+
}
|
|
983
|
+
|
|
962
984
|
function skipTrivia(input: string, cur: number, ctx: ParseContext): number {
|
|
985
|
+
const t = ctx.trivia
|
|
986
|
+
if (t !== undefined && cannotStartTrivia(input, cur, t)) return cur
|
|
963
987
|
const s = SCAN
|
|
964
988
|
if (s !== null
|
|
965
989
|
&& ctx._triviaLog === undefined
|
|
@@ -1041,7 +1065,8 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
1041
1065
|
* into them.
|
|
1042
1066
|
*/
|
|
1043
1067
|
function nextTerm(child: Piece, input: string, cur: number, ctx: ParseContext): number {
|
|
1044
|
-
|
|
1068
|
+
const t = ctx.trivia
|
|
1069
|
+
if (t === undefined || cannotStartTrivia(input, cur, t)) {
|
|
1045
1070
|
const v = child(input, cur, ctx)
|
|
1046
1071
|
if (v === FAIL) return -1
|
|
1047
1072
|
TERMV = v
|
|
@@ -1089,7 +1114,8 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
1089
1114
|
|
|
1090
1115
|
/** A non-first term at a site proven to run under an open node capture buffer. */
|
|
1091
1116
|
function nextTermBuffered(child: Piece, input: string, cur: number, ctx: ParseContext): number {
|
|
1092
|
-
|
|
1117
|
+
const t = ctx.trivia
|
|
1118
|
+
if (t === undefined || cannotStartTrivia(input, cur, t)) {
|
|
1093
1119
|
const v = child(input, cur, ctx)
|
|
1094
1120
|
if (v === FAIL) return -1
|
|
1095
1121
|
TERMV = v
|
|
@@ -2016,6 +2042,9 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
2016
2042
|
// known here, so the scope piece holds the scanner it installs as a `const`.
|
|
2017
2043
|
const scanFor: FastTriviaScanner | null =
|
|
2018
2044
|
swapLegal && ki >= 0 && !triviaLabelled[ki]! ? triviaScan[ki]! : null
|
|
2045
|
+
// The scope installs its slot's lead table with its trivia, so the gate
|
|
2046
|
+
// (`cannotStartTrivia`) never has to look the slot up.
|
|
2047
|
+
const scopeLead = ki < 0 ? null : LEAD_TABLES[ki]!
|
|
2019
2048
|
const child = link(code[ip + 2]!)
|
|
2020
2049
|
// TWO PIECES, SELECTED HERE — not one piece that tests the opcode. Capture
|
|
2021
2050
|
// is fixed for the whole parse, so `code[ip] === OP_SCOPE_CAP` is an
|
|
@@ -2032,6 +2061,9 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
2032
2061
|
const savedScan = SCAN
|
|
2033
2062
|
const savedCap = ctx.captureTrivia
|
|
2034
2063
|
SCAN = scanFor
|
|
2064
|
+
const savedLeadOf = LEAD_OF, savedLead = LEAD_TABLE
|
|
2065
|
+
LEAD_OF = scopeTrivia
|
|
2066
|
+
LEAD_TABLE = scopeLead
|
|
2035
2067
|
ctx.trivia = scopeTrivia
|
|
2036
2068
|
ctx.triviaKindLabels = scopeLabels
|
|
2037
2069
|
ctx.captureTrivia = true
|
|
@@ -2040,6 +2072,8 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
2040
2072
|
ctx.trivia = saved
|
|
2041
2073
|
ctx.triviaKindLabels = savedLabels
|
|
2042
2074
|
SCAN = savedScan
|
|
2075
|
+
LEAD_OF = savedLeadOf
|
|
2076
|
+
LEAD_TABLE = savedLead
|
|
2043
2077
|
return v
|
|
2044
2078
|
}
|
|
2045
2079
|
: (input, pos, ctx) => {
|
|
@@ -2047,12 +2081,17 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
2047
2081
|
const savedLabels = ctx.triviaKindLabels
|
|
2048
2082
|
const savedScan = SCAN
|
|
2049
2083
|
SCAN = scanFor
|
|
2084
|
+
const savedLeadOf = LEAD_OF, savedLead = LEAD_TABLE
|
|
2085
|
+
LEAD_OF = scopeTrivia
|
|
2086
|
+
LEAD_TABLE = scopeLead
|
|
2050
2087
|
ctx.trivia = scopeTrivia
|
|
2051
2088
|
ctx.triviaKindLabels = scopeLabels
|
|
2052
2089
|
const v = child(input, pos, ctx)
|
|
2053
2090
|
ctx.trivia = saved
|
|
2054
2091
|
ctx.triviaKindLabels = savedLabels
|
|
2055
2092
|
SCAN = savedScan
|
|
2093
|
+
LEAD_OF = savedLeadOf
|
|
2094
|
+
LEAD_TABLE = savedLead
|
|
2056
2095
|
return v
|
|
2057
2096
|
}
|
|
2058
2097
|
return scopeRootPolicy(scopePiece, code[ip] === OP_SCOPE_PLAIN ? 0 : code[ip + 3]!)
|
|
@@ -355,10 +355,12 @@ export function compileRuleMap(
|
|
|
355
355
|
const roots = assemblyExtraIps(artifact)
|
|
356
356
|
let siteBytes, fixed
|
|
357
357
|
try {
|
|
358
|
-
|
|
358
|
+
// Priced without the `_errors` marks a lexical program over a scan adds
|
|
359
|
+
// (`scanSinkNeutral`), so they move no site in or out of the budget.
|
|
360
|
+
siteBytes = emitAssemblySource(table, artifact, cfg, roots, true, undefined, true).siteBytes
|
|
359
361
|
// What any selective assembly costs before its first site body: the shared
|
|
360
362
|
// prelude and the factory's tail. A budget that cannot cover it buys nothing.
|
|
361
|
-
fixed = emitAssemblySource(table, artifact, cfg, roots, true, new Set()).source.length
|
|
363
|
+
fixed = emitAssemblySource(table, artifact, cfg, roots, true, new Set(), true).source.length
|
|
362
364
|
} catch (e) {
|
|
363
365
|
if (e instanceof Unemittable) return { ...none, refusal: e.construct }
|
|
364
366
|
throw e
|
|
@@ -384,7 +386,7 @@ export function compileRuleMap(
|
|
|
384
386
|
refusal: `a site within its ${Math.floor(ceiling)} B selection ceiling (the prelude alone is ${fixed} B)`,
|
|
385
387
|
}
|
|
386
388
|
}
|
|
387
|
-
const bytes = emitAssemblySource(table, artifact, cfg, roots, true, sites).source.length
|
|
389
|
+
const bytes = emitAssemblySource(table, artifact, cfg, roots, true, sites, true).source.length
|
|
388
390
|
if (bytes <= ceiling) return { sites, bytes: Math.ceil(bytes * SUPERCOMPILE_GROWTH) }
|
|
389
391
|
// Scale by the overshoot, and by at least 1%: near the ceiling a site is
|
|
390
392
|
// the unit, and the ratio alone can stall a few bytes over it.
|
|
@@ -414,7 +414,7 @@ function emitRollback(t: string, buf: boolean, rawMode: number, s: Sinks = NO_SI
|
|
|
414
414
|
* only the first branch, a known slot keeps only the second (with the scope's own
|
|
415
415
|
* scanner already bound into `skip`), and `TRI_UNKNOWN` keeps both.
|
|
416
416
|
*/
|
|
417
|
-
function emitTerm(callee: string, dst: string, t: string, l: SiteLabel, skip: string): string {
|
|
417
|
+
function emitTerm(callee: string, dst: string, t: string, l: SiteLabel, skip: string, miss?: (c: string) => string): string {
|
|
418
418
|
const fast = `const ${t}v=${callee}(input,cur,ctx)
|
|
419
419
|
if(${t}v===FAIL)return FAIL
|
|
420
420
|
${dst}=${t}v
|
|
@@ -432,7 +432,14 @@ if(${t}v===FAIL)return FAIL
|
|
|
432
432
|
${dst}=${t}v
|
|
433
433
|
if(EC.e>${t}s)cur=EC.e
|
|
434
434
|
else{rollbackScannedTriviaAt(ctx,${t}tl,${t}stl,${t}lg,${t}slg,${t}rt,${t}srt)}`
|
|
435
|
-
|
|
435
|
+
// A character no trivia of this slot can start opens the empty gap, which
|
|
436
|
+
// records nothing, so the term runs at `cur` with no marks around a scan.
|
|
437
|
+
if (l.tri !== TRI_UNKNOWN) return miss === undefined ? scanned : `const ${t}c=input.charCodeAt(cur)
|
|
438
|
+
if(${miss(`${t}c`)}){
|
|
439
|
+
${fast}
|
|
440
|
+
}else{
|
|
441
|
+
${scanned}
|
|
442
|
+
}`
|
|
436
443
|
return `if(ctx.trivia===undefined){
|
|
437
444
|
${fast}
|
|
438
445
|
}else{
|
|
@@ -588,6 +595,13 @@ export function emitAssemblySource(
|
|
|
588
595
|
extraIps: readonly number[] = [],
|
|
589
596
|
staticBuild = false,
|
|
590
597
|
select?: ReadonlySet<number>,
|
|
598
|
+
/**
|
|
599
|
+
* Leave the `_errors` bit of a lexical program over a scan out of the side-sink
|
|
600
|
+
* census below. A budgeted selection is priced and chosen on this lowering, so
|
|
601
|
+
* the marks that bit adds move no site in or out of a budget; the artifact is
|
|
602
|
+
* then emitted with them.
|
|
603
|
+
*/
|
|
604
|
+
scanSinkNeutral = false,
|
|
591
605
|
): EmitResult {
|
|
592
606
|
const { code, k, fx, disp, dsp, triviaLabelled } = t
|
|
593
607
|
const swapLegal = !cfg.trackLines
|
|
@@ -733,13 +747,15 @@ export function emitAssemblySource(
|
|
|
733
747
|
// that otherwise matched byte for byte.
|
|
734
748
|
//
|
|
735
749
|
// Any future row whose child is not an offset in `code` belongs here for the
|
|
736
|
-
// same reason. `OP_LIVE` is the other such row, and it is unemittable.
|
|
750
|
+
// same reason. `OP_LIVE` is the other such row, and it is unemittable. An
|
|
751
|
+
// `OP_LEX_PROGRAM` over a scan (`t.lexPrograms[id].scan`, a whole `balanced()`
|
|
752
|
+
// lowered as one recognizer) runs that same interpreted scan, so it is one too.
|
|
737
753
|
const sinkSites = reachable
|
|
738
754
|
const sinkBits = new Map<number, number>()
|
|
739
755
|
for (const ip of sinkSites) {
|
|
740
756
|
const op = code[ip]
|
|
741
757
|
sinkBits.set(ip,
|
|
742
|
-
op === OP_SCAN ? 3
|
|
758
|
+
op === OP_SCAN || (!scanSinkNeutral && op === OP_LEX_PROGRAM && t.lexPrograms[code[ip + 1]!]!.scan !== undefined) ? 3
|
|
743
759
|
: (op === OP_FIELD ? 1 : 0)
|
|
744
760
|
| (op === OP_EXPECT || (REC && (op === OP_REP || op === OP_REPV)) ? 2 : 0))
|
|
745
761
|
}
|
|
@@ -974,6 +990,19 @@ return 1
|
|
|
974
990
|
return `if(${gateMissTest(child)}){${miss}}\n`
|
|
975
991
|
}
|
|
976
992
|
|
|
993
|
+
/**
|
|
994
|
+
* The test that no trivia of slot `ki` can start at the code unit `c`: the
|
|
995
|
+
* slot's first characters, as code points, decide every code unit below the
|
|
996
|
+
* surrogates; a surrogate, or `NaN` past the end of input, is left to the scan.
|
|
997
|
+
* `undefined` where the first set is unbounded or too wide to print.
|
|
998
|
+
*/
|
|
999
|
+
function triviaMiss(ki: number): ((c: string) => string) | undefined {
|
|
1000
|
+
const leads = triviaLeadRanges(ki, t.trivia)
|
|
1001
|
+
if (leads === undefined || leads.length > 16) return undefined
|
|
1002
|
+
if (leads.length === 0) return c => `${c}<55296`
|
|
1003
|
+
return c => `${c}<55296&&!(${leads.map(r => r.lo === r.hi ? `${c}===${r.lo}` : `(${c}>=${r.lo}&&${c}<=${r.hi})`).join('||')})`
|
|
1004
|
+
}
|
|
1005
|
+
|
|
977
1006
|
function skipFor(l: SiteLabel): string {
|
|
978
1007
|
if (l.tri < 0) return '_skipTrivia'
|
|
979
1008
|
const ki = l.tri
|
|
@@ -991,11 +1020,16 @@ return 1
|
|
|
991
1020
|
if (!hasScan) {
|
|
992
1021
|
// No installed scanner: the labelled/`trackLines` path, which records
|
|
993
1022
|
// through `scanTrivia`. `needsDeferredTriviaCommit` is implied by an open
|
|
994
|
-
// buffer, so an in-node site skips the call that asks.
|
|
1023
|
+
// buffer, so an in-node site skips the call that asks. A character no gap
|
|
1024
|
+
// can start answers first, with no call (`triviaMiss`).
|
|
1025
|
+
const miss = triviaMiss(ki)
|
|
1026
|
+
const pre = miss === undefined ? '' : `const c=input.charCodeAt(cur)
|
|
1027
|
+
if(${miss('c')})return cur
|
|
1028
|
+
`
|
|
995
1029
|
skipDefs.push(l.buf
|
|
996
|
-
? `function ${nm}(input,cur,ctx){return skipDeferredTrivia(input,cur,ctx)}`
|
|
1030
|
+
? `function ${nm}(input,cur,ctx){${pre === '' ? '' : `\n${pre}`}return skipDeferredTrivia(input,cur,ctx)}`
|
|
997
1031
|
: `function ${nm}(input,cur,ctx){
|
|
998
|
-
if(needsDeferredTriviaCommit(ctx))return skipDeferredTrivia(input,cur,ctx)
|
|
1032
|
+
${pre}if(needsDeferredTriviaCommit(ctx))return skipDeferredTrivia(input,cur,ctx)
|
|
999
1033
|
return advanceTrivia(input,cur,ctx)}`)
|
|
1000
1034
|
return nm
|
|
1001
1035
|
}
|
|
@@ -1830,7 +1864,7 @@ return v
|
|
|
1830
1864
|
const callee = leadingTriviaFieldAt(code, k, childIp) ? `(LEAD.at=cur,${kids[i]!})` : kids[i]!
|
|
1831
1865
|
parts.push(holdsGap(code, k, base, n, i, nullable)
|
|
1832
1866
|
? emitHoldTerm(callee, vn, tmp(), L, skipFor(L))
|
|
1833
|
-
: emitTerm(callee, vn, tmp(), L, skipFor(L)))
|
|
1867
|
+
: emitTerm(callee, vn, tmp(), L, skipFor(L), L.tri >= 0 ? triviaMiss(L.tri) : undefined))
|
|
1834
1868
|
}
|
|
1835
1869
|
names.push(vn)
|
|
1836
1870
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Combinator, FirstSet, ParserDef } from '../types.ts'
|
|
2
2
|
import { firstSetOf, matchesEmpty } from '../combinators/first-set.ts'
|
|
3
|
+
import { leadRanges } from '../lead-ranges.ts'
|
|
3
4
|
import { deriveExpected } from '../combinators/expect.ts'
|
|
4
5
|
import { buildReadsState, buildReadsTrivia } from '../compiler/build-arity.ts'
|
|
5
6
|
import {
|
|
@@ -34,7 +35,7 @@ function disjointSets(sets: readonly FirstSet[]): boolean {
|
|
|
34
35
|
const seen: Array<{ lo: number; hi: number }> = []
|
|
35
36
|
for (const fs of sets) {
|
|
36
37
|
if (fs.kind !== 'ranges' || fs.ranges.length === 0) return false
|
|
37
|
-
for (const r of fs.ranges) {
|
|
38
|
+
for (const r of leadRanges(fs.ranges)) {
|
|
38
39
|
for (const p of seen) if (r.lo <= p.hi && p.lo <= r.hi) return false
|
|
39
40
|
seen.push(r)
|
|
40
41
|
}
|
package/src/table/program.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { Combinator, ParseContext, ParseResult } from '../types.ts'
|
|
|
2
2
|
import { classifiedTrivia, trivia as triviaOf } from '../combinators/map.ts'
|
|
3
3
|
import { fastTriviaScanner, type FastTriviaScanner } from '../combinators/trivia-skip.ts'
|
|
4
4
|
import { regex } from '../combinators/regex.ts'
|
|
5
|
+
import { leadRanges } from '../lead-ranges.ts'
|
|
5
6
|
import { choice } from '../combinators/choice.ts'
|
|
6
7
|
import { many } from '../combinators/repeat.ts'
|
|
7
8
|
import type { EmittedFactory, PoolPlan } from './emit-assembly.ts'
|
|
@@ -642,7 +643,7 @@ export function decodeClassSpec(spec: string): Array<{ lo: number; hi: number }>
|
|
|
642
643
|
export function resolveClass(spec: string): ResolvedClass {
|
|
643
644
|
const ascii = new Uint8Array(128)
|
|
644
645
|
const hi: number[] = []
|
|
645
|
-
for (const { lo, hi: up } of decodeClassSpec(spec)) {
|
|
646
|
+
for (const { lo, hi: up } of leadRanges(decodeClassSpec(spec))) {
|
|
646
647
|
if (lo < 128) for (let c = lo; c <= Math.min(up, 127); c++) ascii[c] = 1
|
|
647
648
|
if (up >= 128) hi.push(Math.max(lo, 128), up)
|
|
648
649
|
}
|
package/src/table/scan-shapes.ts
CHANGED
|
@@ -235,6 +235,9 @@ function parseSeqParts(source: string): SeqPart[] | null {
|
|
|
235
235
|
let cp: number
|
|
236
236
|
if (uni) { cp = uni.cp; i = uni.next }
|
|
237
237
|
else if (e === undefined) return null
|
|
238
|
+
// A digit escape is a backreference or a legacy octal escape (`\012`); only a
|
|
239
|
+
// lone `\0` is NUL.
|
|
240
|
+
else if (e >= '0' && e <= '9' && (e !== '0' || (source[i + 2] !== undefined && source[i + 2]! >= '0' && source[i + 2]! <= '9'))) return null
|
|
238
241
|
else if (e in CLASS_ESCAPES) { cp = CLASS_ESCAPES[e]!; i += 2 }
|
|
239
242
|
else if ((e >= 'a' && e <= 'z') || (e >= 'A' && e <= 'Z')) return null
|
|
240
243
|
else { cp = e.codePointAt(0)!; i += 2 }
|
|
@@ -1078,11 +1081,11 @@ function parseScanShapeCore(source: string): ScanShape | null {
|
|
|
1078
1081
|
/**
|
|
1079
1082
|
* Flag-aware wrapper around `parseScanShape`. Lowering to a raw code-point scan
|
|
1080
1083
|
* assumes default regex semantics, so any flag that changes matching (`i` case
|
|
1081
|
-
* folding, `u`
|
|
1084
|
+
* folding, `u`/`v` code points and sets, `m`/`s` anchor/dot behavior) disables it.
|
|
1082
1085
|
* `g`/`y` are stickiness-only and safe.
|
|
1083
1086
|
*/
|
|
1084
1087
|
export function scanShapeFromRegex(source: string, flags: string): ScanShape | null {
|
|
1085
|
-
if (/[
|
|
1088
|
+
if (/[msuv]/.test(flags)) return null
|
|
1086
1089
|
if (/i/.test(flags)) return caseFoldShape(source)
|
|
1087
1090
|
return parseScanShape(source)
|
|
1088
1091
|
}
|
package/src/table/site-labels.ts
CHANGED
|
@@ -65,6 +65,7 @@ import {
|
|
|
65
65
|
OP_SCAN, OP_SCOPE, OP_SCOPE_CAP, OP_SCOPE_PLAIN, OP_SEQ, OP_SEQV, OP_SEQX, OP_TOKEN, OP_XFORM,
|
|
66
66
|
} from './ops.ts'
|
|
67
67
|
import { childSlots } from './child-slots.ts'
|
|
68
|
+
import { leadRanges } from '../lead-ranges.ts'
|
|
68
69
|
|
|
69
70
|
/** No scope on this path installs a known trivia — the lattice's top element. */
|
|
70
71
|
export const TRI_UNKNOWN = -2
|
|
@@ -380,5 +381,5 @@ export function triviaLeadRanges(
|
|
|
380
381
|
const fs = trivia[tri]?._meta.firstSet
|
|
381
382
|
if (fs === undefined) return undefined
|
|
382
383
|
if (fs.kind === 'empty') return []
|
|
383
|
-
return fs.kind === 'ranges' && fs.ranges !== undefined ? fs.ranges : undefined
|
|
384
|
+
return fs.kind === 'ranges' && fs.ranges !== undefined ? leadRanges(fs.ranges) : undefined
|
|
384
385
|
}
|
package/src/table/trivia-gap.ts
CHANGED
|
@@ -48,6 +48,25 @@ export function skipDeferredTrivia(input: string, cur: number, ctx: ParseContext
|
|
|
48
48
|
return scan
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
/**
|
|
52
|
+
* THE CHARACTERS A TRIVIA CAN START WITH, as a table over ASCII: `1` where a run
|
|
53
|
+
* may start, `0` where none can. `null` when the trivia's first set is unbounded.
|
|
54
|
+
*
|
|
55
|
+
* A gap at a character outside the table is the empty gap. It consumes nothing
|
|
56
|
+
* and records nothing in any sink (trivia log, root log, capture buffer, line
|
|
57
|
+
* table), so a skip there can answer `cur` without scanning. A character at or
|
|
58
|
+
* above 128, or past the end of input, is not decided by the table.
|
|
59
|
+
*/
|
|
60
|
+
export function triviaLeadTable(triviaP: Combinator<unknown>): Uint8Array | null {
|
|
61
|
+
const fs = triviaP._meta.firstSet
|
|
62
|
+
if (fs.kind === 'any') return null
|
|
63
|
+
const table = new Uint8Array(128)
|
|
64
|
+
if (fs.kind === 'ranges') {
|
|
65
|
+
for (const r of fs.ranges) for (let c = Math.max(0, r.lo); c <= Math.min(127, r.hi); c++) table[c] = 1
|
|
66
|
+
}
|
|
67
|
+
return table
|
|
68
|
+
}
|
|
69
|
+
|
|
51
70
|
/** The end of the run at `cur` when nothing root-selected lies in it, else −1. */
|
|
52
71
|
function clearRunEnd(input: string, cur: number, ctx: ParseContext, triviaP: Combinator<unknown> | undefined, plain: boolean): number {
|
|
53
72
|
const rootKinds = ctx._rootTriviaKindIndex
|
package/src/version.ts
CHANGED