parseman 0.47.0 → 0.48.0
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 +27 -21
- package/dist/analysis/diagnostics.js +2955 -357
- package/dist/analysis/diagnostics.js.map +3 -3
- package/dist/cli/index.js +2946 -348
- package/dist/cli/index.js.map +3 -3
- package/dist/combinators/expect.d.ts +1 -0
- package/dist/combinators/expect.d.ts.map +1 -1
- package/dist/combinators/expected.d.ts +8 -0
- package/dist/combinators/expected.d.ts.map +1 -0
- package/dist/combinators/first-set.d.ts +8 -0
- package/dist/combinators/first-set.d.ts.map +1 -1
- package/dist/combinators/grammar.d.ts.map +1 -1
- package/dist/combinators/keywords.d.ts +4 -0
- package/dist/combinators/keywords.d.ts.map +1 -1
- package/dist/combinators/literal.d.ts.map +1 -1
- package/dist/combinators/not.d.ts.map +1 -1
- package/dist/combinators/peek.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/sequence.d.ts.map +1 -1
- package/dist/combinators/trivia-skip.d.ts +19 -1
- package/dist/combinators/trivia-skip.d.ts.map +1 -1
- package/dist/compiler/direct-projection.d.ts +12 -0
- package/dist/compiler/direct-projection.d.ts.map +1 -0
- package/dist/compiler/token-alphabet.d.ts +845 -5
- package/dist/compiler/token-alphabet.d.ts.map +1 -1
- package/dist/compiler/token-capability.d.ts +3 -0
- package/dist/compiler/token-capability.d.ts.map +1 -0
- package/dist/compiler/token-capability.js +3192 -0
- package/dist/compiler/token-capability.js.map +6 -0
- package/dist/cst/trivia-charscan.d.ts +3 -0
- package/dist/cst/trivia-charscan.d.ts.map +1 -1
- package/dist/cst/trivia-css-scanner.d.ts +5 -0
- package/dist/cst/trivia-css-scanner.d.ts.map +1 -0
- package/dist/cst/trivia-kinds.d.ts.map +1 -1
- package/dist/index.js +2959 -348
- package/dist/index.js.map +3 -3
- package/dist/language-service/index.js +7 -1
- package/dist/language-service/index.js.map +1 -1
- package/dist/plugin/index.js +2969 -358
- package/dist/plugin/index.js.map +3 -3
- package/dist/spec/index.js +366 -49
- package/dist/spec/index.js.map +2 -2
- package/dist/table/assemble.d.ts.map +1 -1
- package/dist/table/child-slots.d.ts.map +1 -1
- package/dist/table/compile-rule-map.d.ts +3 -1
- package/dist/table/compile-rule-map.d.ts.map +1 -1
- package/dist/table/emit-assembly.d.ts +13 -14
- package/dist/table/emit-assembly.d.ts.map +1 -1
- package/dist/table/emit.d.ts.map +1 -1
- package/dist/table/encode.d.ts.map +1 -1
- package/dist/table/exec-baseline.d.ts.map +1 -1
- package/dist/table/exec.d.ts.map +1 -1
- package/dist/table/index.js +4839 -2232
- package/dist/table/index.js.map +3 -3
- package/dist/table/lex-body.d.ts +9 -0
- package/dist/table/lex-body.d.ts.map +1 -0
- package/dist/table/lex-program.d.ts +25 -0
- package/dist/table/lex-program.d.ts.map +1 -0
- package/dist/table/ops.d.ts +47 -4
- package/dist/table/ops.d.ts.map +1 -1
- package/dist/table/program.d.ts +53 -2
- package/dist/table/program.d.ts.map +1 -1
- package/dist/table/scalar-terminal.d.ts +13 -0
- package/dist/table/scalar-terminal.d.ts.map +1 -0
- package/dist/table/site-labels.d.ts.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +12 -11
- package/src/combinators/expect.ts +1 -0
- package/src/combinators/expected.ts +17 -0
- package/src/combinators/first-set.ts +82 -3
- package/src/combinators/grammar.ts +8 -3
- package/src/combinators/keywords.ts +22 -6
- package/src/combinators/literal.ts +2 -1
- package/src/combinators/not.ts +5 -1
- package/src/combinators/peek.ts +5 -1
- package/src/combinators/regex.ts +5 -3
- package/src/combinators/repeat.ts +4 -10
- package/src/combinators/sequence.ts +35 -16
- package/src/combinators/trivia-skip.ts +75 -11
- package/src/compiler/direct-projection.ts +43 -0
- package/src/compiler/token-alphabet.ts +4316 -12
- package/src/compiler/token-capability.ts +6 -0
- package/src/cst/trivia-charscan.ts +34 -0
- package/src/cst/trivia-css-scanner.ts +142 -0
- package/src/cst/trivia-kinds.ts +3 -1
- package/src/plugin/index.ts +4 -4
- package/src/table/assemble.ts +1224 -222
- package/src/table/child-slots.ts +4 -3
- package/src/table/compile-rule-map.ts +21 -3
- package/src/table/emit-assembly.ts +363 -109
- package/src/table/emit.ts +26 -1
- package/src/table/encode.ts +246 -54
- package/src/table/exec-baseline.ts +3 -7
- package/src/table/exec.ts +140 -18
- package/src/table/lex-body.ts +30 -0
- package/src/table/lex-program.ts +422 -0
- package/src/table/ops.ts +50 -4
- package/src/table/program.ts +67 -2
- package/src/table/scalar-terminal.ts +51 -0
- package/src/table/site-labels.ts +13 -3
- package/src/table/stamp.ts +3 -3
- package/src/types.ts +1 -1
- package/src/version.ts +1 -1
- package/dist/analysis/diagnostics.cjs +0 -13736
- package/dist/analysis/diagnostics.cjs.map +0 -6
- package/dist/cli/index.cjs +0 -14837
- package/dist/cli/index.cjs.map +0 -6
- package/dist/index.cjs +0 -16303
- package/dist/index.cjs.map +0 -6
- package/dist/language-service/index.cjs +0 -1302
- package/dist/language-service/index.cjs.map +0 -6
- package/dist/oracle/index.cjs +0 -254
- package/dist/oracle/index.cjs.map +0 -6
- package/dist/plugin/index.cjs +0 -16009
- package/dist/plugin/index.cjs.map +0 -6
- package/dist/run/index.cjs +0 -577
- package/dist/run/index.cjs.map +0 -6
- package/dist/spec/index.cjs +0 -6188
- package/dist/spec/index.cjs.map +0 -6
- package/dist/table/index.cjs +0 -11375
- package/dist/table/index.cjs.map +0 -6
|
@@ -9,16 +9,11 @@
|
|
|
9
9
|
*
|
|
10
10
|
* ## Why text, and not a rearrangement of `assemble.ts`
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* megamorphic — a hash lookup in the megamorphic stub cache, no inlining, no
|
|
18
|
-
* type feedback for the callee body. No rearrangement of a file that MINTS
|
|
19
|
-
* closures can produce a distinct literal per site. Emitting one
|
|
20
|
-
* `function _pf<N>` per site can: inside `_pf12` the call `_pf7(…)` names ONE
|
|
21
|
-
* binding and stays monomorphic.
|
|
12
|
+
* Emission makes each fixed grammar edge a direct generated identifier and is
|
|
13
|
+
* also the form a build can serialise as an ordinary function literal for CSP.
|
|
14
|
+
* The closure assembler can bind the same topology directly through scalar
|
|
15
|
+
* captures; emission's distinct job is to print that binding, not to assert a
|
|
16
|
+
* V8 property that the semantic design depends on.
|
|
22
17
|
*
|
|
23
18
|
* ## What may be shared, and what must be emitted
|
|
24
19
|
*
|
|
@@ -54,13 +49,15 @@ import {
|
|
|
54
49
|
OP_ADJ, OP_ATTEMPT, OP_CHOICE, OP_DISPATCH, OP_EMPTY, OP_EXPECT, OP_FIELD, OP_GATE,
|
|
55
50
|
OP_LABEL, OP_LEAF, OP_LIT, OP_LIT_CI, OP_LIT_CI_TRACK, OP_LIT_TRACK, OP_NAMES,
|
|
56
51
|
OP_NODE, OP_NODE_TRACK, OP_NOT, OP_OPT, OP_PEEK, OP_REP, OP_REPV, OP_ROUTED, OP_RULE, OP_RX,
|
|
57
|
-
OP_RX_TRACK, OP_SCAN, OP_SCOPE, OP_SCOPE_CAP, OP_SEQ, OP_SEQV, OP_SEQX, OP_TOKEN, OP_XFORM,
|
|
52
|
+
OP_RX_TRACK, OP_SCAN, OP_SCOPE, OP_SCOPE_CAP, OP_SCOPE_PLAIN, OP_SEQ, OP_SEQV, OP_SEQX, OP_TOKEN, OP_XFORM,
|
|
53
|
+
OP_LEX_BODY, OP_LEX_PROGRAM,
|
|
58
54
|
} from './ops.ts'
|
|
59
|
-
import type
|
|
55
|
+
import { validateDispatchSpec, type ResolvedClass, type ResolvedTable, type TableProgram } from './program.ts'
|
|
60
56
|
import { emitShapeMatch, scanShapeFromRegex } from './scan-shapes.ts'
|
|
61
57
|
import {
|
|
62
|
-
CAP_OFF, CAP_ON, TRI_NONE, TRI_UNKNOWN,
|
|
58
|
+
CAP_OFF, CAP_ON, TRI_NONE, TRI_UNKNOWN, computeSiteLabels, reachableSites, type SiteLabel,
|
|
63
59
|
} from './site-labels.ts'
|
|
60
|
+
import { scalarTerminalNodeChild, scalarTerminalNotChild } from './scalar-terminal.ts'
|
|
64
61
|
|
|
65
62
|
/** What the compiled factory hands back — the emitted twin of `Assembly`. */
|
|
66
63
|
export type EmittedPiece = (input: string, pos: number, ctx: ParseContext) => unknown
|
|
@@ -94,7 +91,7 @@ export class Unemittable extends Error {
|
|
|
94
91
|
export const EMITTED_PARAMS = [
|
|
95
92
|
'EC', 'FAIL', 'K', 'FX', 'FNS', 'MASK', 'CLS', 'AFX', 'TRIVIA', 'TRIVIALABELS', 'TRIVIASCAN',
|
|
96
93
|
'SCANS', 'DISP', 'DSP', 'EMPTY_FX', 'EMPTY_CH', 'EMPTY_TLOG', 'EMPTY_TL',
|
|
97
|
-
'cstCaptureActive', 'pushCstLeaf', 'pushCstChild', 'rollbackTriviaAt', 'failAt',
|
|
94
|
+
'cstCaptureActive', 'pushCstLeaf', 'pushCstChild', 'rollbackTriviaAt', 'rollbackScannedTriviaAt', 'failAt',
|
|
98
95
|
'classHas', 'consumeTrivia', 'buildFieldMap', 'projectChild', 'unwrapChild',
|
|
99
96
|
'demoteCapturedToRaw', 'cstLeavesLen', 'skipTriviaScanned', 'needsDeferredTriviaCommit',
|
|
100
97
|
'scanTrivia', 'advanceTrivia', 'refuseUnclassifiedRootScope', 'spanLines', 'rawEntry', 'lead',
|
|
@@ -106,6 +103,16 @@ export const EMITTED_PARAMS = [
|
|
|
106
103
|
// span, expected set and CST embedding are produced by the SAME code in every
|
|
107
104
|
// engine and cannot drift.
|
|
108
105
|
'SENTS', 'matchesAt', 'recoverScan', 'orSentinel', 'captureError',
|
|
106
|
+
// Pure scalar terminal recognizers, indexed by the terminal's existing const
|
|
107
|
+
// operand. Appended for compatibility with older precompiled factories.
|
|
108
|
+
'RECOG',
|
|
109
|
+
// Appended so precompiled factories produced by earlier runtimes keep every
|
|
110
|
+
// positional helper binding. Older factories ignore this trailing argument.
|
|
111
|
+
'commitTriviaScan', 'scanTriviaCompact', 'LEX',
|
|
112
|
+
// Appended for old precompiled-factory ABI: old factories ignore it.
|
|
113
|
+
'adjacencyHolds',
|
|
114
|
+
// Appended for selected composite lexical programs; old factories ignore it.
|
|
115
|
+
'LEXPROG',
|
|
109
116
|
] as const
|
|
110
117
|
|
|
111
118
|
/**
|
|
@@ -131,7 +138,7 @@ function _skipTrivia(input,cur,ctx){
|
|
|
131
138
|
const s=_pfScan
|
|
132
139
|
if(s!==null&&ctx._triviaLog===undefined&&!(ctx.captureTrivia===true&&(ctx._cstBuf!==undefined||ctx._cstTriviaLog!==undefined)))return s(input,cur)
|
|
133
140
|
if(s!==null)return skipTriviaScanned(s,input,cur,ctx)
|
|
134
|
-
if(needsDeferredTriviaCommit(ctx))
|
|
141
|
+
if(needsDeferredTriviaCommit(ctx))return commitTriviaScan(scanTriviaCompact(input,cur,ctx))
|
|
135
142
|
return advanceTrivia(input,cur,ctx)
|
|
136
143
|
}
|
|
137
144
|
function _pushLeaf(ctx,value,s,e){pushCstLeaf(ctx,{_tag:'leaf',value,span:{start:s,end:e}})}
|
|
@@ -145,6 +152,13 @@ if(b.raw!==undefined)b.raw.push(l)
|
|
|
145
152
|
else if(b.rawSingle!==undefined){b.raw=[b.rawSingle,l];b.rawSingle=undefined}
|
|
146
153
|
else b.rawSingle=l
|
|
147
154
|
}
|
|
155
|
+
function _capturedFlatChildren(children){
|
|
156
|
+
if(children.length===0)return EMPTY_CH
|
|
157
|
+
if(children[0]!==undefined)return children
|
|
158
|
+
let first=1
|
|
159
|
+
while(first<children.length&&children[first]===undefined)first++
|
|
160
|
+
return first===children.length?EMPTY_CH:children.slice(first)
|
|
161
|
+
}
|
|
148
162
|
function _accSet(ax,acc){
|
|
149
163
|
if(ax===undefined||ax.length===0)return acc
|
|
150
164
|
if(acc===undefined)return ax.slice()
|
|
@@ -311,19 +325,24 @@ function emitRollback(t: string, buf: boolean, s: Sinks = NO_SINKS): string {
|
|
|
311
325
|
* only the first branch, a known slot keeps only the second (with the scope's own
|
|
312
326
|
* scanner already bound into `skip`), and `TRI_UNKNOWN` keeps both.
|
|
313
327
|
*/
|
|
314
|
-
function emitTerm(callee: string, dst: string, t: string, l: SiteLabel, skip: string
|
|
328
|
+
function emitTerm(callee: string, dst: string, t: string, l: SiteLabel, skip: string): string {
|
|
315
329
|
const fast = `const ${t}v=${callee}(input,cur,ctx)
|
|
316
330
|
if(${t}v===FAIL)return FAIL
|
|
317
331
|
${dst}=${t}v
|
|
318
332
|
cur=EC.e`
|
|
319
333
|
if (l.tri === TRI_NONE) return fast
|
|
320
|
-
const scanned =
|
|
334
|
+
const scanned = `const ${t}tl=ctx._cstBuf!==undefined?(ctx._cstBuf.tl!==undefined?ctx._cstBuf.tl.length:0):(ctx._cstTriviaLog!==undefined?ctx._cstTriviaLog.length:0)
|
|
335
|
+
const ${t}lg=ctx._triviaLog!==undefined?ctx._triviaLog.length:0
|
|
336
|
+
const ${t}rt=ctx._rootTriviaLog!==undefined?ctx._rootTriviaLog.length:0
|
|
321
337
|
const ${t}s=${skip}(input,cur,ctx)
|
|
338
|
+
const ${t}stl=ctx._cstBuf!==undefined?(ctx._cstBuf.tl!==undefined?ctx._cstBuf.tl.length:0):(ctx._cstTriviaLog!==undefined?ctx._cstTriviaLog.length:0)
|
|
339
|
+
const ${t}slg=ctx._triviaLog!==undefined?ctx._triviaLog.length:0
|
|
340
|
+
const ${t}srt=ctx._rootTriviaLog!==undefined?ctx._rootTriviaLog.length:0
|
|
322
341
|
const ${t}v=${callee}(input,${t}s,ctx)
|
|
323
342
|
if(${t}v===FAIL)return FAIL
|
|
324
343
|
${dst}=${t}v
|
|
325
344
|
if(EC.e>${t}s)cur=EC.e
|
|
326
|
-
else{
|
|
345
|
+
else{rollbackScannedTriviaAt(ctx,${t}tl,${t}stl,${t}lg,${t}slg,${t}rt,${t}srt)}`
|
|
327
346
|
if (l.tri !== TRI_UNKNOWN) return scanned
|
|
328
347
|
return `if(ctx.trivia===undefined){
|
|
329
348
|
${fast}
|
|
@@ -365,7 +384,11 @@ export type PoolPlan = {
|
|
|
365
384
|
readonly classes: readonly (readonly number[])[]
|
|
366
385
|
/** Per `AFX` row: the `fx` index of each arm's expected set. */
|
|
367
386
|
readonly armExpected: readonly (readonly number[])[]
|
|
368
|
-
/**
|
|
387
|
+
/**
|
|
388
|
+
* Per `MASK` row: a non-negative legacy `CLS` row index, or `~dispIndex` for
|
|
389
|
+
* a directly bound choice. The negative form names the EXISTING resolved
|
|
390
|
+
* dispatch row rather than serialising a second copy of its arm classes.
|
|
391
|
+
*/
|
|
369
392
|
readonly masks: readonly number[]
|
|
370
393
|
}
|
|
371
394
|
|
|
@@ -403,13 +426,18 @@ function maskForClassRow(gates: readonly (ResolvedClass | null)[]): Uint32Array
|
|
|
403
426
|
export function rebuildPools(
|
|
404
427
|
cc: readonly ResolvedClass[],
|
|
405
428
|
fx: readonly (readonly string[])[],
|
|
429
|
+
disp: ResolvedTable['disp'],
|
|
406
430
|
plan: PoolPlan,
|
|
407
431
|
): { masks: Uint32Array[]; classes: (ResolvedClass | null)[][]; armExpected: (readonly string[])[][] } {
|
|
408
432
|
const classes = plan.classes.map(row => row.map(i => (i < 0 ? null : cc[i] ?? null)))
|
|
409
433
|
return {
|
|
410
434
|
classes,
|
|
411
435
|
armExpected: plan.armExpected.map(row => row.map(i => fx[i] ?? [])),
|
|
412
|
-
masks: plan.masks.map(
|
|
436
|
+
masks: plan.masks.map(source => {
|
|
437
|
+
const row = source >= 0 ? classes[source] : disp[~source]?.armCls
|
|
438
|
+
if (row === undefined) throw new TypeError(`table emitter: invalid MASK plan source ${source}`)
|
|
439
|
+
return maskForClassRow(row)
|
|
440
|
+
}),
|
|
413
441
|
}
|
|
414
442
|
}
|
|
415
443
|
|
|
@@ -471,6 +499,17 @@ export function emitAssemblySource(
|
|
|
471
499
|
// emitted scope supplies a context this pass cannot see.
|
|
472
500
|
const roots = [...Object.values(prog.rules), ...extraIps]
|
|
473
501
|
const labels = computeSiteLabels(code, roots, hostCst)
|
|
502
|
+
// Eligibility is pooled by the terminal's existing constant operand, matching
|
|
503
|
+
// the closure recognizer pool: distinct terminal rows sharing one spec share
|
|
504
|
+
// both the recognizer and the ordinary-terminal lowering.
|
|
505
|
+
const scalarSpecs = new Set<number>()
|
|
506
|
+
if (!hostCst && !cfg.tolerant && !cfg.probe && !cfg.coverage && !cfg.trackLines) {
|
|
507
|
+
for (const ip of reachableSites(code, roots)) {
|
|
508
|
+
if (code[ip] !== OP_NODE) continue
|
|
509
|
+
const child = scalarTerminalNodeChild(code, ip)
|
|
510
|
+
if (child >= 0) scalarSpecs.add(code[child + 1]!)
|
|
511
|
+
}
|
|
512
|
+
}
|
|
474
513
|
|
|
475
514
|
// THE SIDE-SINK CENSUS, over the same reachable set the labels are computed
|
|
476
515
|
// from. `OP_FIELD` is the only ROW that writes `_fields` and `OP_EXPECT` the
|
|
@@ -515,6 +554,7 @@ export function emitAssemblySource(
|
|
|
515
554
|
const reached = new Set<number>()
|
|
516
555
|
const prelude: string[] = []
|
|
517
556
|
const skipDefs: string[] = []
|
|
557
|
+
const choiceDefs: string[] = []
|
|
518
558
|
const skipPool = new Map<string, string>()
|
|
519
559
|
const pool = new Map<string, string>()
|
|
520
560
|
const masks: Uint32Array[] = []
|
|
@@ -543,6 +583,7 @@ export function emitAssemblySource(
|
|
|
543
583
|
const kRef = (i: number): string => hoist('k', `K[${i}]`)
|
|
544
584
|
const fxRef = (i: number): string => hoist('fx', `FX[${i}]`)
|
|
545
585
|
const fnRef = (i: number): string => hoist('fn', `FNS[${i}]`)
|
|
586
|
+
const recognizerRef = (i: number): string => hoist('rec', `RECOG[${i}]`)
|
|
546
587
|
/**
|
|
547
588
|
* The sync sentinel for a char-class index, hoisted once per class.
|
|
548
589
|
*
|
|
@@ -593,9 +634,9 @@ export function emitAssemblySource(
|
|
|
593
634
|
// through `scanTrivia`. `needsDeferredTriviaCommit` is implied by an open
|
|
594
635
|
// buffer, so an in-node site skips the call that asks.
|
|
595
636
|
skipDefs.push(l.buf
|
|
596
|
-
? `function ${nm}(input,cur,ctx){
|
|
637
|
+
? `function ${nm}(input,cur,ctx){return commitTriviaScan(scanTriviaCompact(input,cur,ctx))}`
|
|
597
638
|
: `function ${nm}(input,cur,ctx){
|
|
598
|
-
if(needsDeferredTriviaCommit(ctx))
|
|
639
|
+
if(needsDeferredTriviaCommit(ctx))return commitTriviaScan(scanTriviaCompact(input,cur,ctx))
|
|
599
640
|
return advanceTrivia(input,cur,ctx)}`)
|
|
600
641
|
return nm
|
|
601
642
|
}
|
|
@@ -729,22 +770,22 @@ ${cfg.probe ? `failAt(ctx,${xf},pos)\n` : ''}return FAIL
|
|
|
729
770
|
if (op === OP_GATE && (cfg.tolerant || cfg.probe)) return code[ip + 2]!
|
|
730
771
|
if (op === OP_RULE) return code[ip + 1]!
|
|
731
772
|
// A SCOPE THAT INSTALLS WHAT IS ALREADY INSTALLED. `encode.ts:520` wraps
|
|
732
|
-
// EVERY rule of a `rules({ trivia }, …)` map in its own `
|
|
773
|
+
// EVERY rule of a `rules({ trivia }, …)` map in its own `OP_SCOPE_PLAIN`, so a
|
|
733
774
|
// grammar with one ambient trivia re-installs the same slot at every rule
|
|
734
775
|
// entry — six context stores, a scanner swap and their six restores, per
|
|
735
776
|
// call, to arrive at the values already there.
|
|
736
777
|
//
|
|
737
778
|
// The label is what makes this decidable: `tri >= 0` can ONLY have come from
|
|
738
|
-
// an enclosing
|
|
779
|
+
// an enclosing scope carrying that slot, and that scope set
|
|
739
780
|
// `ctx.triviaKindLabels` and `_pfScan` from the same slot, so all three are
|
|
740
|
-
// already the values this row would write.
|
|
741
|
-
//
|
|
742
|
-
//
|
|
781
|
+
// already the values this row would write. `OP_SCOPE_PLAIN` has no root
|
|
782
|
+
// policy by construction; a policy-bearing `OP_SCOPE` aliases only when its
|
|
783
|
+
// literal policy is zero. `OP_SCOPE_CAP` also raises `captureTrivia`.
|
|
743
784
|
//
|
|
744
785
|
// `ki >= 0` is required rather than implied: `TRI_NONE` and `TRI_UNKNOWN` are
|
|
745
786
|
// themselves negative, so comparing a negative operand against a lattice
|
|
746
787
|
// element would read "unknown" as a match.
|
|
747
|
-
if (op === OP_SCOPE && code[ip + 3]! === 0 && code[ip + 1]! >= 0
|
|
788
|
+
if ((op === OP_SCOPE_PLAIN || (op === OP_SCOPE && code[ip + 3]! === 0)) && code[ip + 1]! >= 0
|
|
748
789
|
&& labels.at(ip).tri === code[ip + 1]!) {
|
|
749
790
|
return code[ip + 2]!
|
|
750
791
|
}
|
|
@@ -783,6 +824,19 @@ ${cfg.probe ? `failAt(ctx,${xf},pos)\n` : ''}return FAIL
|
|
|
783
824
|
const s = k[code[ip + 1]!] as string
|
|
784
825
|
const xf = fxRef(code[ip + 2]!)
|
|
785
826
|
const track = op === OP_LIT_TRACK
|
|
827
|
+
if (!track && scalarSpecs.has(code[ip + 1]!)) {
|
|
828
|
+
const recognize = recognizerRef(code[ip + 1]!)
|
|
829
|
+
return `${head}
|
|
830
|
+
const e=${recognize}(input,pos)
|
|
831
|
+
if(e>=0){
|
|
832
|
+
${captureLeaf(q(s))}
|
|
833
|
+
EC.e=e
|
|
834
|
+
return ${q(s)}
|
|
835
|
+
}
|
|
836
|
+
ctx._fe=pos;ctx._fx=${xf}
|
|
837
|
+
return FAIL
|
|
838
|
+
}`
|
|
839
|
+
}
|
|
786
840
|
// LENGTH IS TABLE DATA, so the compare is chosen here — the emitted twin
|
|
787
841
|
// of the closure engine's length-keyed literal bodies.
|
|
788
842
|
const test = s.length === 1
|
|
@@ -857,6 +911,20 @@ ${cfg.probe ? `failAt(ctx,${xf},pos)\n` : ''}return FAIL
|
|
|
857
911
|
case OP_RX_TRACK: {
|
|
858
912
|
const xf = fxRef(code[ip + 2]!)
|
|
859
913
|
const track = op === OP_RX_TRACK
|
|
914
|
+
if (!track && scalarSpecs.has(code[ip + 1]!)) {
|
|
915
|
+
const recognize = recognizerRef(code[ip + 1]!)
|
|
916
|
+
return `${head}
|
|
917
|
+
const e=${recognize}(input,pos)
|
|
918
|
+
if(e>=0){
|
|
919
|
+
const v=input.slice(pos,e)
|
|
920
|
+
${captureLeaf('v')}
|
|
921
|
+
EC.e=e
|
|
922
|
+
return v
|
|
923
|
+
}
|
|
924
|
+
ctx._fe=pos;ctx._fx=${xf}
|
|
925
|
+
return FAIL
|
|
926
|
+
}`
|
|
927
|
+
}
|
|
860
928
|
// THE MATCH ARRAY IS THE COST, not the matching. `re.exec` allocates one
|
|
861
929
|
// per row — 6,005 rows per `json/document` parse, 12.9% of everything
|
|
862
930
|
// executed — and every field of it but `[0]` is discarded here. A shape
|
|
@@ -1016,10 +1084,11 @@ return r.value
|
|
|
1016
1084
|
}
|
|
1017
1085
|
|
|
1018
1086
|
case OP_SCOPE:
|
|
1019
|
-
case OP_SCOPE_CAP:
|
|
1087
|
+
case OP_SCOPE_CAP:
|
|
1088
|
+
case OP_SCOPE_PLAIN: {
|
|
1020
1089
|
const ki = code[ip + 1]!
|
|
1021
1090
|
const cap = op === OP_SCOPE_CAP
|
|
1022
|
-
const flags = code[ip + 3]!
|
|
1091
|
+
const flags = op === OP_SCOPE_PLAIN ? 0 : code[ip + 3]!
|
|
1023
1092
|
const child = link(code[ip + 2]!)
|
|
1024
1093
|
// THE SWAP, RESOLVED AT EMIT. `swapLegal` is `!trackLines`, an option;
|
|
1025
1094
|
// the other two are table data. All three are known here, so the body
|
|
@@ -1103,6 +1172,75 @@ return out
|
|
|
1103
1172
|
}`
|
|
1104
1173
|
}
|
|
1105
1174
|
|
|
1175
|
+
case OP_LEX_BODY: {
|
|
1176
|
+
const recognize = hoist('lex', `LEX[${code[ip + 1]!}]`)
|
|
1177
|
+
const expected = fxRef(code[ip + 2]!)
|
|
1178
|
+
const suffixExpected = fxRef(code[ip + 3]!)
|
|
1179
|
+
const lineFlags = code[ip + 4]!
|
|
1180
|
+
const hasSuffix = (lineFlags & 4) !== 0
|
|
1181
|
+
return `${head}
|
|
1182
|
+
const r=${recognize}(input,pos)
|
|
1183
|
+
if(r<0){ctx._fe=pos;ctx._fx=${expected};if(ctx._probe!==undefined)failAt(ctx,${expected},pos);return FAIL}
|
|
1184
|
+
const sm=${hasSuffix ? 'r%2===1' : 'false'},e=(r-(sm?1:0))/2
|
|
1185
|
+
${(lineFlags & 1) !== 0 ? '_trackLines(ctx,input,sm?e-1:e)' : ''}
|
|
1186
|
+
${hasSuffix ? 'ctx._fc=false' : ''}
|
|
1187
|
+
${hasSuffix && (lineFlags & 2) !== 0 ? 'if(sm)_trackLines(ctx,input,e)' : ''}
|
|
1188
|
+
${hasSuffix ? `if(!sm){ctx._fe=e;ctx._fx=${suffixExpected};if(ctx._probe!==undefined)failAt(ctx,${suffixExpected},e)}` : ''}
|
|
1189
|
+
const v=input.slice(pos,e)
|
|
1190
|
+
if(ctx._cstBuf!==undefined||ctx._cstLeaves!==undefined)pushCstLeaf(ctx,{_tag:'leaf',value:v,span:{start:pos,end:e}})
|
|
1191
|
+
EC.e=e
|
|
1192
|
+
return v
|
|
1193
|
+
}`
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
case OP_LEX_PROGRAM: {
|
|
1197
|
+
const programId = code[ip + 1]!
|
|
1198
|
+
const run = hoist('lexProgram', `LEXPROG[${programId}]`)
|
|
1199
|
+
const scanId = t.lexPrograms[programId]!.scan
|
|
1200
|
+
if (scanId !== undefined) return `${head}
|
|
1201
|
+
const sTri=ctx.trivia,sKinds=ctx.triviaKindLabels,sScan=_pfScan
|
|
1202
|
+
const sBuf=ctx._cstBuf,sCh=ctx._cstChildren,sLv=ctx._cstLeaves,sRaw=ctx._cstRawChildren,sTl=ctx._cstTriviaLog
|
|
1203
|
+
const sOtl=ctx._triviaLog,sRtl=ctx._rootTriviaLog
|
|
1204
|
+
const wasCap=ctx._cstBuf!==undefined||ctx._cstLeaves!==undefined
|
|
1205
|
+
_pfScan=null
|
|
1206
|
+
ctx.trivia=undefined
|
|
1207
|
+
ctx.triviaKindLabels=undefined
|
|
1208
|
+
ctx._cstBuf=undefined
|
|
1209
|
+
ctx._cstChildren=undefined
|
|
1210
|
+
ctx._cstLeaves=undefined
|
|
1211
|
+
ctx._cstRawChildren=undefined
|
|
1212
|
+
ctx._cstTriviaLog=undefined
|
|
1213
|
+
ctx._triviaLog=undefined
|
|
1214
|
+
ctx._rootTriviaLog=undefined
|
|
1215
|
+
let e
|
|
1216
|
+
try{e=${run}(input,pos,ctx,SCANS[${scanId}])}finally{
|
|
1217
|
+
_pfScan=sScan
|
|
1218
|
+
ctx.trivia=sTri
|
|
1219
|
+
ctx.triviaKindLabels=sKinds
|
|
1220
|
+
ctx._cstBuf=sBuf
|
|
1221
|
+
ctx._cstChildren=sCh
|
|
1222
|
+
ctx._cstLeaves=sLv
|
|
1223
|
+
ctx._cstRawChildren=sRaw
|
|
1224
|
+
ctx._cstTriviaLog=sTl
|
|
1225
|
+
ctx._triviaLog=sOtl
|
|
1226
|
+
ctx._rootTriviaLog=sRtl
|
|
1227
|
+
}
|
|
1228
|
+
if(e<0)return FAIL
|
|
1229
|
+
const v=input.slice(pos,e)
|
|
1230
|
+
if(wasCap)pushCstLeaf(ctx,{_tag:'leaf',value:v,span:{start:pos,end:e}})
|
|
1231
|
+
EC.e=e
|
|
1232
|
+
return v
|
|
1233
|
+
}`
|
|
1234
|
+
return `${head}
|
|
1235
|
+
const e=${run}(input,pos,ctx)
|
|
1236
|
+
if(e<0)return FAIL
|
|
1237
|
+
const v=input.slice(pos,e)
|
|
1238
|
+
if(ctx._cstBuf!==undefined||ctx._cstLeaves!==undefined)pushCstLeaf(ctx,{_tag:'leaf',value:v,span:{start:pos,end:e}})
|
|
1239
|
+
EC.e=e
|
|
1240
|
+
return v
|
|
1241
|
+
}`
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1106
1244
|
/* ── transaction ─────────────────────────────────────────────────────── */
|
|
1107
1245
|
|
|
1108
1246
|
case OP_ATTEMPT: {
|
|
@@ -1120,6 +1258,18 @@ return FAIL
|
|
|
1120
1258
|
}
|
|
1121
1259
|
|
|
1122
1260
|
case OP_NOT: {
|
|
1261
|
+
const scalarChild = scalarTerminalNotChild(code, ip)
|
|
1262
|
+
if (scalarChild >= 0) {
|
|
1263
|
+
const recognize = recognizerRef(code[scalarChild + 1]!)
|
|
1264
|
+
const xf = fxRef(code[ip + 2]!)
|
|
1265
|
+
return `${head}
|
|
1266
|
+
if(${recognize}(input,pos)<0){EC.e=pos;return null}
|
|
1267
|
+
ctx._fe=pos
|
|
1268
|
+
ctx._fx=${xf}
|
|
1269
|
+
EC.e=pos
|
|
1270
|
+
return FAIL
|
|
1271
|
+
}`
|
|
1272
|
+
}
|
|
1123
1273
|
const child = link(code[ip + 1]!)
|
|
1124
1274
|
const xf = fxRef(code[ip + 2]!)
|
|
1125
1275
|
const p = tmp()
|
|
@@ -1172,19 +1322,14 @@ return v
|
|
|
1172
1322
|
const base = fused ? ip + 3 : ip + 2
|
|
1173
1323
|
const n = code[fused ? ip + 2 : ip + 1]!
|
|
1174
1324
|
const wantValues = op !== OP_SEQV
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
// position finds the gap already consumed and answers "adjacent" every
|
|
1179
|
-
// time, SILENTLY (`ops.ts:226-251`).
|
|
1180
|
-
for (let i = 1; i < n; i++) {
|
|
1181
|
-
if (code[code[base + i]!] === OP_ADJ) {
|
|
1182
|
-
throw new Unemittable('a sequence carrying an adjacency assertion (OP_ADJ)')
|
|
1183
|
-
}
|
|
1184
|
-
}
|
|
1185
|
-
const fn = fused ? fnRef(code[ip + 1]!) : undefined
|
|
1325
|
+
const reducer = fused ? code[ip + 1]! : -1
|
|
1326
|
+
const projection = reducer < 0 ? ~reducer : -1
|
|
1327
|
+
const fn = fused && projection < 0 ? fnRef(reducer) : undefined
|
|
1186
1328
|
const kids: string[] = []
|
|
1187
|
-
for (let i = 0; i < n; i++)
|
|
1329
|
+
for (let i = 0; i < n; i++) {
|
|
1330
|
+
const childIp = code[base + i]!
|
|
1331
|
+
kids.push(code[childIp] === OP_ADJ ? '' : link(childIp))
|
|
1332
|
+
}
|
|
1188
1333
|
/**
|
|
1189
1334
|
* A RECOVERY SEQUENCE PUBLISHES `ctx._sync`, and that is the WHOLE of
|
|
1190
1335
|
* "recovery config" — no grammar carries any (`assemble.ts:1446`).
|
|
@@ -1219,7 +1364,9 @@ return v
|
|
|
1219
1364
|
parts.push(`const v0=${kids[0]}(input,pos,ctx)`, 'if(v0===FAIL)return FAIL')
|
|
1220
1365
|
const close = (): string => REC ? `${parts.join('\n')}\n}finally{ctx._sync=${sy}}\n}` : `${parts.join('\n')}\n}`
|
|
1221
1366
|
if (n === 1) {
|
|
1222
|
-
parts.push(fused
|
|
1367
|
+
parts.push(fused
|
|
1368
|
+
? projection === 0 ? 'return v0' : `return ${fn}([v0],{start:pos,end:EC.e})`
|
|
1369
|
+
: wantValues ? 'return [v0]' : 'return undefined')
|
|
1223
1370
|
return close()
|
|
1224
1371
|
}
|
|
1225
1372
|
parts.push('let cur=EC.e')
|
|
@@ -1228,12 +1375,22 @@ return v
|
|
|
1228
1375
|
const vn = `v${i}`
|
|
1229
1376
|
parts.push(`let ${vn}`)
|
|
1230
1377
|
if (REC) parts.push(pub(i))
|
|
1231
|
-
|
|
1378
|
+
const childIp = code[base + i]!
|
|
1379
|
+
if (code[childIp] === OP_ADJ) {
|
|
1380
|
+
const negated = code[childIp + 1] === 1
|
|
1381
|
+
const ki = code[childIp + 2]!
|
|
1382
|
+
const kinds = ki < 0 ? 'undefined' : `K[${ki}]`
|
|
1383
|
+
const expected = fxRef(code[childIp + 3]!)
|
|
1384
|
+
parts.push(
|
|
1385
|
+
`if(!adjacencyHolds(input,cur,ctx,${negated ? 'true' : 'false'},${kinds})){ctx._fe=cur;ctx._fx=${expected};return FAIL}`,
|
|
1386
|
+
`${vn}=null`,
|
|
1387
|
+
)
|
|
1388
|
+
} else parts.push(emitTerm(kids[i]!, vn, tmp(), L, skipFor(L)))
|
|
1232
1389
|
names.push(vn)
|
|
1233
1390
|
}
|
|
1234
1391
|
parts.push('EC.e=cur')
|
|
1235
1392
|
parts.push(fused
|
|
1236
|
-
? `return ${fn}([${names.join(',')}],{start:pos,end:cur})`
|
|
1393
|
+
? projection >= 0 ? `return ${names[projection]}` : `return ${fn}([${names.join(',')}],{start:pos,end:cur})`
|
|
1237
1394
|
: wantValues ? `return [${names.join(',')}]` : 'return undefined')
|
|
1238
1395
|
return close()
|
|
1239
1396
|
}
|
|
@@ -1245,11 +1402,6 @@ return v
|
|
|
1245
1402
|
const base = ip + 4
|
|
1246
1403
|
const arms: string[] = []
|
|
1247
1404
|
for (let i = 0; i < n; i++) arms.push(link(code[base + i]!))
|
|
1248
|
-
const axi = armExpected.push(
|
|
1249
|
-
Array.from({ length: n }, (_, i) => t.fx[code[base + n + i]!] as readonly string[]),
|
|
1250
|
-
) - 1
|
|
1251
|
-
armExpectedPlan.push(Array.from({ length: n }, (_, i) => code[base + n + i]!))
|
|
1252
|
-
const afx = hoist('afx', `AFX[${axi}]`)
|
|
1253
1405
|
|
|
1254
1406
|
if (table.exclusive) {
|
|
1255
1407
|
// NO OPEN ARMS EXIST under `exclusive` — `resolveDispatch` clears the
|
|
@@ -1283,52 +1435,51 @@ return FAIL
|
|
|
1283
1435
|
}`
|
|
1284
1436
|
}
|
|
1285
1437
|
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
//
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
if((bits&${1 << i})!==0){
|
|
1438
|
+
{
|
|
1439
|
+
// Direct topology for every fixed arity. The emitted body names every
|
|
1440
|
+
// child, gate and expected set; no AFX/CLS row survives merely to feed
|
|
1441
|
+
// an indexed winner back into one call site.
|
|
1442
|
+
const di = code[ip + 1]!
|
|
1443
|
+
const expected = Array.from({ length: n }, (_, i) => fxRef(code[base + n + i]!))
|
|
1444
|
+
const gates = Array.from({ length: n }, (_, i) => table.armCls[i] ?? null)
|
|
1445
|
+
const gateRefs = Array.from({ length: n }, (_, i) => hoist('g', `DISP[${di}].armCls[${i}]`))
|
|
1446
|
+
const maskable = n <= 32
|
|
1447
|
+
const maskName = maskable
|
|
1448
|
+
? hoist('mk', `MASK[${masks.push(maskForClassRow(gates)) - 1}]`)
|
|
1449
|
+
: ''
|
|
1450
|
+
// `~di` points back to the authoritative dispatch row. A precompiled
|
|
1451
|
+
// module reconstructs only the input-indexed mask from that row; it
|
|
1452
|
+
// cannot carry a second mutable answer for the arms' classes.
|
|
1453
|
+
if (maskable) maskPlan.push(~di)
|
|
1454
|
+
const p = tmp()
|
|
1455
|
+
const catchName = maskable ? `_cx${uid++}_` : ''
|
|
1456
|
+
if (maskable) {
|
|
1457
|
+
const catchCases = expected.map((e, i) =>
|
|
1458
|
+
`case ${i}:if(target<=${i})return acc;acc=_accSet(${e},acc)`).join('\n')
|
|
1459
|
+
choiceDefs.push(`function ${catchName}(target,prev,acc){switch(prev){\n${catchCases}\n}return acc}`)
|
|
1460
|
+
}
|
|
1461
|
+
const maskArms = maskable ? arms.map((arm, i) => {
|
|
1462
|
+
return `if((bits&${1 << i})!==0){
|
|
1311
1463
|
ctx._fc=false
|
|
1312
|
-
{const v=${
|
|
1464
|
+
{const v=${arm}(input,pos,ctx)
|
|
1313
1465
|
if(v!==FAIL)return v}
|
|
1314
|
-
|
|
1466
|
+
acc=${catchName}(${i},prev,acc)
|
|
1315
1467
|
prev=${i + 1}
|
|
1316
1468
|
acc=_accSet(ctx._fx,acc)
|
|
1317
1469
|
if(ctx._fc===true){if(acc!==undefined)ctx._fx=acc;return FAIL}
|
|
1318
1470
|
${emitRollback(p, L.buf, sinks)}
|
|
1319
|
-
}`
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
if(${gRef}[${i}]===null||classHas(${gRef}[${i}],c)){
|
|
1471
|
+
}`
|
|
1472
|
+
}).join('\n') : ''
|
|
1473
|
+
const generalArms = arms.map((arm, i) => `if(${gateRefs[i]}===null||classHas(${gateRefs[i]},c)){
|
|
1323
1474
|
ctx._fc=false
|
|
1324
|
-
{const v=${
|
|
1475
|
+
{const v=${arm}(input,pos,ctx)
|
|
1325
1476
|
if(v!==FAIL)return v}
|
|
1326
1477
|
acc=_accSet(ctx._fx,acc)
|
|
1327
1478
|
if(ctx._fc===true){if(acc!==undefined)ctx._fx=acc;return FAIL}
|
|
1328
1479
|
${emitRollback(p, L.buf, sinks)}
|
|
1329
|
-
}else acc=_accSet(${
|
|
1480
|
+
}else acc=_accSet(${expected[i]},acc)`).join('\n')
|
|
1330
1481
|
|
|
1331
|
-
|
|
1482
|
+
return `${head}
|
|
1332
1483
|
const c=lead(input,pos)
|
|
1333
1484
|
${emitMark(p, L.buf, sinks)}
|
|
1334
1485
|
let acc
|
|
@@ -1336,45 +1487,58 @@ ${maskable ? `if(c<128){
|
|
|
1336
1487
|
const bits=${maskName}[c<0?128:c]
|
|
1337
1488
|
let prev=0
|
|
1338
1489
|
${maskArms}
|
|
1339
|
-
|
|
1490
|
+
acc=${catchName}(${n},prev,acc)
|
|
1340
1491
|
ctx._fe=pos;ctx._fx=acc??${choiceFx}
|
|
1341
1492
|
return FAIL
|
|
1342
1493
|
}
|
|
1343
|
-
` : ''}
|
|
1494
|
+
` : ''}
|
|
1495
|
+
${generalArms}
|
|
1344
1496
|
ctx._fe=pos;ctx._fx=acc??${choiceFx}
|
|
1345
1497
|
return FAIL
|
|
1346
1498
|
}`
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1347
1501
|
}
|
|
1348
1502
|
|
|
1349
1503
|
case OP_DISPATCH: {
|
|
1350
1504
|
const di = code[ip + 2]!
|
|
1351
|
-
const spec = dsp[di]
|
|
1505
|
+
const spec = dsp[di]
|
|
1352
1506
|
const selector = link(code[ip + 1]!)
|
|
1353
1507
|
const otherIp = code[ip + 3]!
|
|
1354
1508
|
const other = otherIp >= 0 ? link(otherIp) : undefined
|
|
1355
1509
|
const otherRouted = code[ip + 4]! === 1
|
|
1356
1510
|
const n = code[ip + 5]!
|
|
1511
|
+
validateDispatchSpec(spec, n, code[ip + 4]!)
|
|
1357
1512
|
const armBase = ip + 6
|
|
1358
1513
|
const arms: string[] = []
|
|
1359
1514
|
for (let i = 0; i < n; i++) arms.push(link(code[armBase + i]!))
|
|
1360
1515
|
const bk = hoist('bk', `DSP[${di}].byKey`)
|
|
1361
|
-
const rt = hoist('rt', `DSP[${di}].routed`)
|
|
1362
1516
|
const dx = hoist('dx', `DSP[${di}].expected`)
|
|
1363
1517
|
// THE MATCHER ARMS, AS SOURCE. `exec.ts`'s `linkMatcher` mints one
|
|
1364
1518
|
// closure per arm from four literals it already has in hand; the four
|
|
1365
1519
|
// are TABLE DATA, so here they are the test itself and no closure, no
|
|
1366
1520
|
// pool and no indexed call exist. Kinds are `matcherClaims`'s exactly
|
|
1367
1521
|
// — 0/1 raw prefix/suffix, 3/4 the pre-folded pair, anything else a
|
|
1368
|
-
// regex.
|
|
1369
|
-
//
|
|
1370
|
-
//
|
|
1522
|
+
// regex. Public `matches()` refuses global/sticky patterns, so its
|
|
1523
|
+
// RegExp can be compiled once into the assembly prelude. Hand-built
|
|
1524
|
+
// low-level rows with g/y or invalid flags retain the old per-test
|
|
1525
|
+
// construction and therefore the old `lastIndex`/throw behavior.
|
|
1371
1526
|
const claims = (m: readonly [number, string, string, number]): string => {
|
|
1372
1527
|
switch (m[0]) {
|
|
1373
1528
|
case 0: return `key.startsWith(${q(m[1])})`
|
|
1374
1529
|
case 1: return `key.endsWith(${q(m[1])})`
|
|
1375
1530
|
case 3: return `asciiFoldKey(key).startsWith(${q(m[1])})`
|
|
1376
1531
|
case 4: return `asciiFoldKey(key).endsWith(${q(m[1])})`
|
|
1377
|
-
default:
|
|
1532
|
+
default: {
|
|
1533
|
+
if (!m[2].includes('g') && !m[2].includes('y')) {
|
|
1534
|
+
try {
|
|
1535
|
+
// Validate without changing malformed-row error timing.
|
|
1536
|
+
new RegExp(m[1], m[2])
|
|
1537
|
+
return `${hoist('dm', `new RegExp(${q(m[1])},${q(m[2])})`)}.test(key)`
|
|
1538
|
+
} catch {}
|
|
1539
|
+
}
|
|
1540
|
+
return `new RegExp(${q(m[1])},${q(m[2])}).test(key)`
|
|
1541
|
+
}
|
|
1378
1542
|
}
|
|
1379
1543
|
}
|
|
1380
1544
|
const chain = spec.match.length === 0
|
|
@@ -1385,6 +1549,19 @@ return FAIL
|
|
|
1385
1549
|
: ''
|
|
1386
1550
|
const m1 = tmp()
|
|
1387
1551
|
const m2 = tmp()
|
|
1552
|
+
const routedCall = (target: string): string => `{const savedRouted=ctx._routed
|
|
1553
|
+
${emitRollback(m1, L.buf, sinks)}
|
|
1554
|
+
${emitMark(m2, L.buf, sinks, false)}
|
|
1555
|
+
ctx._routed={value:key,span:{start:pos,end:selEnd}}
|
|
1556
|
+
try{v=${target}(input,pos,ctx)}finally{ctx._routed=savedRouted}
|
|
1557
|
+
break}`
|
|
1558
|
+
const plainCall = (target: string): string => `v=${target}(input,selEnd,ctx);break`
|
|
1559
|
+
const armCases = arms.map((target, i) => `case ${i}:${spec.routed[i] === 1
|
|
1560
|
+
? routedCall(target)
|
|
1561
|
+
: plainCall(target)}`).join('\n')
|
|
1562
|
+
const fallbackCase = other === undefined ? '' : `default:${otherRouted
|
|
1563
|
+
? routedCall(other)
|
|
1564
|
+
: plainCall(other)}`
|
|
1388
1565
|
// THE SELECTOR RUNS ONCE and the key it returns picks the arm — that is
|
|
1389
1566
|
// what `dispatch()` buys over a choice of arms that each re-parse the
|
|
1390
1567
|
// opener. A routed arm rewinds the selector's trivia capture and gets
|
|
@@ -1396,24 +1573,16 @@ if(sv===FAIL)return FAIL
|
|
|
1396
1573
|
const selEnd=EC.e
|
|
1397
1574
|
const key=sv
|
|
1398
1575
|
let arm=${bk}.get(key)
|
|
1399
|
-
${fold}${chain}
|
|
1576
|
+
${fold}${chain}
|
|
1400
1577
|
if(arm===undefined){
|
|
1401
|
-
${other === undefined ? `ctx._fe=selEnd;ctx._fx=${dx};return FAIL` :
|
|
1402
|
-
}else ur=${rt}[arm]===1
|
|
1403
|
-
const savedRouted=ctx._routed
|
|
1404
|
-
${emitMark(m2, L.buf, sinks)}
|
|
1405
|
-
if(ur){
|
|
1406
|
-
${emitRollback(m1, L.buf, sinks)}
|
|
1407
|
-
${emitMark(m2, L.buf, sinks, false)}
|
|
1408
|
-
ctx._routed={value:key,span:{start:pos,end:selEnd}}
|
|
1578
|
+
${other === undefined ? `ctx._fe=selEnd;ctx._fx=${dx};return FAIL` : ''}
|
|
1409
1579
|
}
|
|
1410
|
-
|
|
1580
|
+
${emitMark(m2, L.buf, sinks)}
|
|
1411
1581
|
let v
|
|
1412
1582
|
switch(arm){
|
|
1413
|
-
${
|
|
1414
|
-
${
|
|
1583
|
+
${armCases}
|
|
1584
|
+
${fallbackCase}
|
|
1415
1585
|
}
|
|
1416
|
-
if(ur)ctx._routed=savedRouted
|
|
1417
1586
|
if(v===FAIL){
|
|
1418
1587
|
${emitRollback(m2, L.buf, sinks)}
|
|
1419
1588
|
ctx._fc=true
|
|
@@ -1436,6 +1605,15 @@ return [key,v]
|
|
|
1436
1605
|
const reportItem = (flags & 4) !== 0
|
|
1437
1606
|
const itemFx = reportItem ? fxRef(code[ip + 6]!) : 'EMPTY_FX'
|
|
1438
1607
|
const collect = op === OP_REP
|
|
1608
|
+
let itemCls: string | undefined
|
|
1609
|
+
if (!REC) {
|
|
1610
|
+
const itemClassIndex = sepIp < 0 ? code[ip + 7]! : -1
|
|
1611
|
+
if (itemClassIndex >= 0) {
|
|
1612
|
+
const ci = classes.push([t.cc[itemClassIndex]!]) - 1
|
|
1613
|
+
classPlan.push([itemClassIndex])
|
|
1614
|
+
itemCls = hoist('ri', `CLS[${ci}][0]`)
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1439
1617
|
const skipBeforeFirst = sepIp < 0 && min === 0
|
|
1440
1618
|
const p = tmp()
|
|
1441
1619
|
// `viaRepItem` is `sep === undefined && count >= min && (count > 0 ||
|
|
@@ -1455,7 +1633,6 @@ return [key,v]
|
|
|
1455
1633
|
const skip = skipFor(L)
|
|
1456
1634
|
const knownTrivia = L.tri === TRI_NONE ? false : L.tri !== TRI_UNKNOWN ? true : undefined
|
|
1457
1635
|
const hasTrivia = knownTrivia === undefined ? 'hasTrivia' : String(knownTrivia)
|
|
1458
|
-
const needMark = L.buf ? 'true' : 'needMark'
|
|
1459
1636
|
// THE `_fields` AND `_errors` MARKS ARE TAKEN ONLY WHERE THE TABLE HAS A
|
|
1460
1637
|
// WRITER, which is the same emit-time census `emitMark` applies to the
|
|
1461
1638
|
// non-loop marks. Before `OP_FIELD` and `OP_EXPECT` were emittable the
|
|
@@ -1541,10 +1718,10 @@ continue
|
|
|
1541
1718
|
`
|
|
1542
1719
|
return `${head}
|
|
1543
1720
|
const out=${collect ? '[]' : 'undefined'}
|
|
1544
|
-
${knownTrivia === undefined ? 'const hasTrivia=ctx.trivia!==undefined\n' : ''}${L.buf ? '' : 'const needMark=_rollbackNeeded(ctx)\n'}${REC ? `const ${my}=ctx._sync\n` : ''}let cur=pos
|
|
1721
|
+
${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
|
|
1545
1722
|
let count=0
|
|
1546
1723
|
for(;;){
|
|
1547
|
-
${max >= 0 ? `if(count>=${max})break\n` : ''}${sep !== undefined ? `if(count>0&&count>=${min}&&cur>=input.length)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` : ''}
|
|
1724
|
+
${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'}&&!classHas(${itemCls},lead(input,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` : ''}
|
|
1548
1725
|
${markBody}
|
|
1549
1726
|
let itemStart=cur
|
|
1550
1727
|
let sepEnd=-1
|
|
@@ -1566,7 +1743,11 @@ itemStart=${hasTrivia === 'false' ? 'EC.e' : hasTrivia === 'true' ? `${skip}(inp
|
|
|
1566
1743
|
${rb}
|
|
1567
1744
|
${trailingAllowed ? 'if(sepEnd>=0)cur=sepEnd\n' : ''}break
|
|
1568
1745
|
}
|
|
1569
|
-
|
|
1746
|
+
${itemCls === undefined ? '' : `if(count>=${min}&&${p}gate&&!classHas(${itemCls},lead(input,itemStart))){
|
|
1747
|
+
${rb}
|
|
1748
|
+
${trailingAllowed ? 'if(sepEnd>=0)cur=sepEnd\n' : ''}break
|
|
1749
|
+
}
|
|
1750
|
+
`}ctx._fc=false
|
|
1570
1751
|
const v=${child}(input,itemStart,ctx)
|
|
1571
1752
|
if(v===FAIL){
|
|
1572
1753
|
${REC
|
|
@@ -1603,6 +1784,26 @@ return out
|
|
|
1603
1784
|
case OP_NODE:
|
|
1604
1785
|
case OP_NODE_TRACK: {
|
|
1605
1786
|
const flags = code[ip + 3]!
|
|
1787
|
+
const scalarChild = scalarTerminalNodeChild(code, ip)
|
|
1788
|
+
if (scalarChild >= 0 && scalarSpecs.has(code[scalarChild + 1]!)) {
|
|
1789
|
+
const recognize = recognizerRef(code[scalarChild + 1]!)
|
|
1790
|
+
const spec = code[scalarChild] === OP_RX ? null : k[code[scalarChild + 1]!] as string
|
|
1791
|
+
const value = spec === null ? 'input.slice(pos,end)' : q(spec)
|
|
1792
|
+
const xf = fxRef(code[scalarChild + 2]!)
|
|
1793
|
+
const build = fnRef(code[ip + 1]!)
|
|
1794
|
+
return `${head}
|
|
1795
|
+
const end=${recognize}(input,pos)
|
|
1796
|
+
if(end<0){ctx._fe=pos;ctx._fx=${xf};return FAIL}
|
|
1797
|
+
const value=${value}
|
|
1798
|
+
const leaf={_tag:'leaf',value,span:{start:pos,end}}
|
|
1799
|
+
const kids=[leaf],rawKids=[leaf],span={start:pos,end}
|
|
1800
|
+
EC.e=end
|
|
1801
|
+
const nd=${build}(kids,undefined,span,rawKids,EMPTY_TL,undefined)
|
|
1802
|
+
${L.buf ? 'pushCstChild(ctx,nd,rawEntry(nd,input,pos,end))' : 'if(ctx._cstBuf!==undefined||ctx._cstChildren!==undefined)pushCstChild(ctx,nd,rawEntry(nd,input,pos,end))'}
|
|
1803
|
+
EC.e=end
|
|
1804
|
+
return nd
|
|
1805
|
+
}`
|
|
1806
|
+
}
|
|
1606
1807
|
const child = link(code[ip + 2]!)
|
|
1607
1808
|
const proj = code[ip + 4]!
|
|
1608
1809
|
const buildIdx = code[ip + 1]!
|
|
@@ -1620,6 +1821,58 @@ return out
|
|
|
1620
1821
|
// interpreter's node case — the most-executed non-terminal in any of
|
|
1621
1822
|
// these grammars. It selects the emitted shape instead.
|
|
1622
1823
|
const build = buildIdx >= 0 ? fnRef(buildIdx) : undefined
|
|
1824
|
+
// The closure assembler's direct-builder capture tiers are table facts,
|
|
1825
|
+
// not closure-only semantics. Print the same body for a precompiled
|
|
1826
|
+
// assembly so a large composeLeaf artifact does not fall back to opening
|
|
1827
|
+
// the generic raw/trivia buffer that its reducer arity proved unread.
|
|
1828
|
+
if (!hostCst && !tracked && build !== undefined && proj < 0
|
|
1829
|
+
&& (flags === 2 || flags === 18 || flags === 34)) {
|
|
1830
|
+
const fields = flags === 18
|
|
1831
|
+
const collapseChildren = flags === 34
|
|
1832
|
+
const publish = `if(sBuf!==undefined){
|
|
1833
|
+
if(sBuf.rawOnly!==true){
|
|
1834
|
+
if(sBuf.ch!==undefined)sBuf.ch.push(nd)
|
|
1835
|
+
else if(sBuf.single!==undefined){sBuf.ch=[sBuf.single,nd];sBuf.single=undefined}
|
|
1836
|
+
else sBuf.single=nd
|
|
1837
|
+
}
|
|
1838
|
+
const rawNd=rawEntry(nd,input,pos,end)
|
|
1839
|
+
if(sBuf.raw!==undefined)sBuf.raw.push(rawNd)
|
|
1840
|
+
else if(sBuf.rawSingle!==undefined){sBuf.raw=[sBuf.rawSingle,rawNd];sBuf.rawSingle=undefined}
|
|
1841
|
+
else sBuf.rawSingle=rawNd
|
|
1842
|
+
}else if(sCh!==undefined){
|
|
1843
|
+
sCh.push(nd)
|
|
1844
|
+
if(sRaw!==undefined)sRaw.push(rawEntry(nd,input,pos,end))
|
|
1845
|
+
}`
|
|
1846
|
+
return `${head}
|
|
1847
|
+
const sCh=ctx._cstChildren,sLv=ctx._cstLeaves,sRaw=ctx._cstRawChildren,sTl=ctx._cstTriviaLog
|
|
1848
|
+
const sCap=ctx.captureTrivia,sBuf=ctx._cstBuf,sFields=ctx._fields
|
|
1849
|
+
const flat=[]
|
|
1850
|
+
ctx._cstBuf=undefined
|
|
1851
|
+
ctx._cstChildren=flat
|
|
1852
|
+
ctx._cstLeaves=flat
|
|
1853
|
+
ctx._cstRawChildren=undefined
|
|
1854
|
+
ctx._cstTriviaLog=undefined
|
|
1855
|
+
ctx.captureTrivia=false
|
|
1856
|
+
ctx._fields=${fields ? '[]' : 'undefined'}
|
|
1857
|
+
const v=${child}(input,pos,ctx)
|
|
1858
|
+
const captured=_capturedFlatChildren(flat)
|
|
1859
|
+
${fields ? 'const fieldMap=buildFieldMap(ctx._fields)\n' : ''}ctx._fields=sFields
|
|
1860
|
+
ctx._cstBuf=sBuf
|
|
1861
|
+
ctx._cstChildren=sCh
|
|
1862
|
+
ctx._cstLeaves=sLv
|
|
1863
|
+
ctx._cstRawChildren=sRaw
|
|
1864
|
+
ctx._cstTriviaLog=sTl
|
|
1865
|
+
ctx.captureTrivia=sCap
|
|
1866
|
+
if(v===FAIL)return FAIL
|
|
1867
|
+
const end=EC.e
|
|
1868
|
+
${collapseChildren
|
|
1869
|
+
? `const nd=captured.length===1?captured[0]:${build}(captured,undefined,{start:pos,end},EMPTY_CH,EMPTY_TL,undefined)`
|
|
1870
|
+
: `const nd=${build}(captured,${fields ? 'fieldMap' : 'undefined'},{start:pos,end},EMPTY_CH,EMPTY_TL,undefined)`}
|
|
1871
|
+
${publish}
|
|
1872
|
+
EC.e=end
|
|
1873
|
+
return nd
|
|
1874
|
+
}`
|
|
1875
|
+
}
|
|
1623
1876
|
const structural = build === undefined && proj < 0
|
|
1624
1877
|
const grammarCapture = (flags & 1) !== 0 || trailingTrivia
|
|
1625
1878
|
const hostCapturesThisType = structural && cfg.hostCaptureTrivia !== undefined
|
|
@@ -1734,6 +1987,7 @@ return nd
|
|
|
1734
1987
|
const source = `${RUNTIME_PRELUDE}
|
|
1735
1988
|
${prelude.join('\n')}
|
|
1736
1989
|
${skipDefs.join('\n')}
|
|
1990
|
+
${choiceDefs.join('\n')}
|
|
1737
1991
|
${bodies.join('\n')}
|
|
1738
1992
|
function _begin(ctx){
|
|
1739
1993
|
const host=ctx.build
|