parseman 0.49.0 → 0.50.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/dist/analysis/commitment.d.ts +2 -1
- package/dist/analysis/commitment.d.ts.map +1 -1
- package/dist/analysis/diagnostics.js +1931 -861
- package/dist/analysis/diagnostics.js.map +3 -3
- package/dist/analysis/rebuild.d.ts.map +1 -1
- package/dist/cli/index.js +1235 -164
- package/dist/cli/index.js.map +3 -3
- package/dist/combinators/trivia-skip.d.ts +1 -1
- package/dist/combinators/trivia-skip.d.ts.map +1 -1
- package/dist/compiler/build-arity.d.ts +16 -0
- package/dist/compiler/build-arity.d.ts.map +1 -1
- package/dist/compiler/degradation.d.ts +0 -2
- package/dist/compiler/degradation.d.ts.map +1 -1
- package/dist/compiler/ir-serialize.d.ts.map +1 -1
- package/dist/compiler/token-alphabet.d.ts +11 -0
- package/dist/compiler/token-alphabet.d.ts.map +1 -1
- package/dist/compiler/token-capability.js +185 -20
- package/dist/compiler/token-capability.js.map +1 -1
- package/dist/cst/capture-buffer.d.ts +5 -1
- package/dist/cst/capture-buffer.d.ts.map +1 -1
- package/dist/index.js +1348 -279
- package/dist/index.js.map +3 -3
- package/dist/language-service/index.js +2 -1
- package/dist/language-service/index.js.map +1 -1
- package/dist/plugin/entry-supercompiler.d.ts +48 -0
- package/dist/plugin/entry-supercompiler.d.ts.map +1 -0
- package/dist/plugin/evaluator.d.ts.map +1 -1
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +1940 -385
- package/dist/plugin/index.js.map +3 -3
- package/dist/run/index.js +2 -1
- package/dist/run/index.js.map +1 -1
- package/dist/spec/index.js +28 -7
- package/dist/spec/index.js.map +1 -1
- package/dist/table/assemble.d.ts.map +1 -1
- package/dist/table/compile-rule-map.d.ts +6 -5
- package/dist/table/compile-rule-map.d.ts.map +1 -1
- package/dist/table/emit-assembly.d.ts +6 -1
- package/dist/table/emit-assembly.d.ts.map +1 -1
- package/dist/table/encode.d.ts.map +1 -1
- package/dist/table/index.js +1298 -231
- package/dist/table/index.js.map +3 -3
- package/dist/table/ops.d.ts +3 -2
- package/dist/table/ops.d.ts.map +1 -1
- package/dist/table/program.d.ts +15 -1
- package/dist/table/program.d.ts.map +1 -1
- package/dist/table/scalar-terminal.d.ts +30 -0
- package/dist/table/scalar-terminal.d.ts.map +1 -1
- package/dist/table/site-labels.d.ts +8 -0
- package/dist/table/site-labels.d.ts.map +1 -1
- package/dist/types.d.ts +2 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
- package/src/analysis/commitment.ts +19 -8
- package/src/analysis/rebuild.ts +1 -0
- package/src/combinators/trivia-skip.ts +2 -2
- package/src/compiler/build-arity.ts +50 -0
- package/src/compiler/degradation.ts +0 -6
- package/src/compiler/ir-serialize.ts +6 -4
- package/src/compiler/token-alphabet.ts +322 -28
- package/src/cst/capture-buffer.ts +34 -11
- package/src/plugin/entry-supercompiler.ts +567 -0
- package/src/plugin/evaluator.ts +10 -0
- package/src/plugin/index.ts +7 -3
- package/src/table/assemble.ts +125 -42
- package/src/table/compile-rule-map.ts +8 -7
- package/src/table/compile.ts +7 -8
- package/src/table/emit-assembly.ts +646 -126
- package/src/table/emit.ts +1 -1
- package/src/table/encode.ts +132 -7
- package/src/table/ops.ts +3 -2
- package/src/table/program.ts +44 -3
- package/src/table/scalar-terminal.ts +213 -1
- package/src/table/site-labels.ts +22 -9
- package/src/types.ts +1 -1
- package/src/version.ts +1 -1
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
* what `encode.ts:1208-1213` refuses to allow for `OP_LIVE`.
|
|
46
46
|
*/
|
|
47
47
|
import type { ParseContext } from '../types.ts'
|
|
48
|
+
import { regexCanMatchEmpty } from '../regex/first-set.ts'
|
|
48
49
|
import {
|
|
49
50
|
OP_ADJ, OP_ATTEMPT, OP_CHOICE, OP_DISPATCH, OP_EMPTY, OP_EXPECT, OP_FIELD, OP_GATE,
|
|
50
51
|
OP_LABEL, OP_LEAF, OP_LIT, OP_LIT_CI, OP_LIT_CI_TRACK, OP_LIT_TRACK, OP_NAMES,
|
|
@@ -52,12 +53,20 @@ import {
|
|
|
52
53
|
OP_RX_TRACK, OP_SCAN, OP_SCOPE, OP_SCOPE_CAP, OP_SCOPE_PLAIN, OP_SEQ, OP_SEQV, OP_SEQX, OP_TOKEN, OP_XFORM,
|
|
53
54
|
OP_LEX_BODY, OP_LEX_PROGRAM,
|
|
54
55
|
} from './ops.ts'
|
|
55
|
-
import {
|
|
56
|
+
import {
|
|
57
|
+
choiceCannotCommit, choiceRollbackMask, failureRollbackClean, validateDispatchSpec,
|
|
58
|
+
type ResolvedClass, type ResolvedTable, type TableProgram,
|
|
59
|
+
} from './program.ts'
|
|
56
60
|
import { emitShapeMatch, scanShapeFromRegex } from './scan-shapes.ts'
|
|
57
61
|
import {
|
|
58
|
-
CAP_OFF, CAP_ON,
|
|
62
|
+
CAP_OFF, CAP_ON, RAW_CAPTURE, RAW_OMIT, TRI_NONE, TRI_UNKNOWN,
|
|
63
|
+
computeSiteLabels, reachableSites, type SiteLabel,
|
|
59
64
|
} from './site-labels.ts'
|
|
60
|
-
import {
|
|
65
|
+
import {
|
|
66
|
+
leadingLiteralFamily, leadingScalarSequence, leadingScalarTerminal,
|
|
67
|
+
scalarTerminalNodeChild, scalarTerminalNotChild, type LeadingScalarSequence,
|
|
68
|
+
} from './scalar-terminal.ts'
|
|
69
|
+
import { childSlots } from './child-slots.ts'
|
|
61
70
|
|
|
62
71
|
/** What the compiled factory hands back — the emitted twin of `Assembly`. */
|
|
63
72
|
export type EmittedPiece = (input: string, pos: number, ctx: ParseContext) => unknown
|
|
@@ -134,6 +143,16 @@ let _pfScan=null
|
|
|
134
143
|
let _pfHost
|
|
135
144
|
let _pfDepth=0
|
|
136
145
|
const _pfFrames=[]
|
|
146
|
+
let _pfTokInput
|
|
147
|
+
let _pfTokPos=-1
|
|
148
|
+
let _pfTokBody=-1
|
|
149
|
+
let _pfTokPacked=-1
|
|
150
|
+
let _pfTokValue
|
|
151
|
+
let _pfTokDispatch=-1
|
|
152
|
+
let _pfTokArm=-1
|
|
153
|
+
let _pfTokEnd=-1
|
|
154
|
+
let _pfSeqEnd=-1
|
|
155
|
+
function _asciiFoldCode(c){return c>=65&&c<=90?c+32:c}
|
|
137
156
|
function _skipTrivia(input,cur,ctx){
|
|
138
157
|
const s=_pfScan
|
|
139
158
|
if(s!==null&&ctx._triviaLog===undefined&&!(ctx.captureTrivia===true&&(ctx._cstBuf!==undefined||ctx._cstTriviaLog!==undefined)))return s(input,cur)
|
|
@@ -200,6 +219,42 @@ if(ctx._rootTriviaLog!==undefined&&ctx._rootTriviaLog.length!==rt)ctx._rootTrivi
|
|
|
200
219
|
}
|
|
201
220
|
`
|
|
202
221
|
|
|
222
|
+
/** Printed only into assemblies that open a count-only raw buffer. */
|
|
223
|
+
const NO_RAW_RUNTIME_PRELUDE = `
|
|
224
|
+
function _pushLeafNoRawBuf(ctx,value,s,e){
|
|
225
|
+
const l={_tag:'leaf',value,span:{start:s,end:e}}
|
|
226
|
+
const b=ctx._cstBuf
|
|
227
|
+
if(b.ch!==undefined)b.ch.push(l)
|
|
228
|
+
else if(b.single!==undefined){b.ch=[b.single,l];b.single=undefined}
|
|
229
|
+
else b.single=l
|
|
230
|
+
b.rawLen++
|
|
231
|
+
}
|
|
232
|
+
function _pushNodeNoRawBuf(ctx,value){
|
|
233
|
+
const b=ctx._cstBuf
|
|
234
|
+
if(b.ch!==undefined)b.ch.push(value)
|
|
235
|
+
else if(b.single!==undefined){b.ch=[b.single,value];b.single=undefined}
|
|
236
|
+
else b.single=value
|
|
237
|
+
b.rawLen++
|
|
238
|
+
}
|
|
239
|
+
function _rbNoRawBuf(ctx,raw,tl,lv,lg,rt){
|
|
240
|
+
const b=ctx._cstBuf
|
|
241
|
+
if(b.rawLen!==raw)b.rawLen=raw
|
|
242
|
+
const ch=b.ch
|
|
243
|
+
if(ch!==undefined){
|
|
244
|
+
if(lv===0)b.ch=undefined
|
|
245
|
+
else if(lv===1){b.single=ch[0];b.ch=undefined}
|
|
246
|
+
else if(ch.length!==lv)ch.length=lv
|
|
247
|
+
}else if(lv===0)b.single=undefined
|
|
248
|
+
const bt=b.tl
|
|
249
|
+
if(bt!==undefined){
|
|
250
|
+
if(tl===0)b.tl=undefined
|
|
251
|
+
else if(bt.length!==tl)bt.length=tl
|
|
252
|
+
}
|
|
253
|
+
if(ctx._triviaLog!==undefined&&ctx._triviaLog.length!==lg)ctx._triviaLog.length=lg
|
|
254
|
+
if(ctx._rootTriviaLog!==undefined&&ctx._rootTriviaLog.length!==rt)ctx._rootTriviaLog.length=rt
|
|
255
|
+
}
|
|
256
|
+
`
|
|
257
|
+
|
|
203
258
|
function q(s: string): string {
|
|
204
259
|
return JSON.stringify(s)
|
|
205
260
|
}
|
|
@@ -240,7 +295,7 @@ function sinkReads(t: string, s: Sinks): string {
|
|
|
240
295
|
return `${s.fd ? `\n${t}fd=ctx._fields!==undefined?ctx._fields.length:0` : ''}${s.er ? `\n${t}er=ctx._errors!==undefined?ctx._errors.length:0` : ''}`
|
|
241
296
|
}
|
|
242
297
|
|
|
243
|
-
function emitMark(t: string, buf: boolean, s: Sinks = NO_SINKS, decl = true): string {
|
|
298
|
+
function emitMark(t: string, buf: boolean, rawMode: number, s: Sinks = NO_SINKS, decl = true): string {
|
|
244
299
|
// THE SITE IS INSIDE A NODE. `OP_NODE` opens `ctx._cstBuf` unconditionally and
|
|
245
300
|
// closes it on the way out, so the whole discriminating chain below — which
|
|
246
301
|
// sink is live, and whether a mark is needed at all — has ONE answer here, and
|
|
@@ -249,8 +304,13 @@ function emitMark(t: string, buf: boolean, s: Sinks = NO_SINKS, decl = true): st
|
|
|
249
304
|
// `decl === false` RE-TAKES a mark into locals that already exist —
|
|
250
305
|
// `OP_DISPATCH` marks, rolls back to the selector's mark, and marks again.
|
|
251
306
|
if (buf) {
|
|
307
|
+
const raw = rawMode === RAW_OMIT
|
|
308
|
+
? `${t}raw=b.rawLen`
|
|
309
|
+
: rawMode === RAW_CAPTURE
|
|
310
|
+
? `const r=b.raw;${t}raw=r!==undefined?r.length:b.rawSingle!==undefined?1:0`
|
|
311
|
+
: `const r=b.raw;${t}raw=b.noRaw===true?b.rawLen:(r!==undefined?r.length:b.rawSingle!==undefined?1:0)`
|
|
252
312
|
return `${decl ? `let ${t}raw=0,${t}tl=0,${t}lv=0,${t}lg=0,${t}rt=0${sinkSlots(t, s)}\n` : ''}{const b=ctx._cstBuf
|
|
253
|
-
|
|
313
|
+
${raw}
|
|
254
314
|
const h=b.ch;${t}lv=h!==undefined?h.length:b.single!==undefined?1:0
|
|
255
315
|
const l=b.tl;${t}tl=l!==undefined?l.length:0
|
|
256
316
|
${t}lg=ctx._triviaLog!==undefined?ctx._triviaLog.length:0
|
|
@@ -258,7 +318,7 @@ ${t}rt=ctx._rootTriviaLog!==undefined?ctx._rootTriviaLog.length:0${sinkReads(t,
|
|
|
258
318
|
}
|
|
259
319
|
return `${decl ? `let ${t}n=false,${t}raw=0,${t}tl=0,${t}lv=0,${t}lg=0,${t}rt=0${sinkSlots(t, s)}\n` : `${t}n=false\n`}{const b=ctx._cstBuf
|
|
260
320
|
if(b!==undefined){
|
|
261
|
-
const r=b.raw;${t}raw=r!==undefined?r.length:b.rawSingle!==undefined?1:0
|
|
321
|
+
const r=b.raw;${t}raw=b.noRaw===true?b.rawLen:(r!==undefined?r.length:b.rawSingle!==undefined?1:0)
|
|
262
322
|
const h=b.ch;${t}lv=h!==undefined?h.length:b.single!==undefined?1:0
|
|
263
323
|
const l=b.tl;${t}tl=l!==undefined?l.length:0
|
|
264
324
|
${t}n=true
|
|
@@ -286,14 +346,12 @@ ${t}rt=ctx._rootTriviaLog!==undefined?ctx._rootTriviaLog.length:0${sinkReads(t,
|
|
|
286
346
|
* than a per-parse guess, and it is why `assemble.ts`'s `nextTerm` still takes
|
|
287
347
|
* all seven: it is the engine those constructs actually run in.
|
|
288
348
|
*
|
|
289
|
-
*
|
|
290
|
-
*
|
|
291
|
-
* `
|
|
292
|
-
*
|
|
293
|
-
* `_fields` has no such guard — `length = undefined` would throw — so it takes
|
|
294
|
-
* the literal `0` that a per-node `_fields` no `OP_FIELD` can push to always has.
|
|
349
|
+
* Both sinks use `undefined` as "no mark taken". A literal zero is not a safe
|
|
350
|
+
* substitute: a sink-free speculative subtree may run inside a node whose
|
|
351
|
+
* enclosing `_fields` already contains entries, and rolling that array back to
|
|
352
|
+
* zero would erase state the subtree did not create.
|
|
295
353
|
*/
|
|
296
|
-
function emitRollback(t: string, buf: boolean, s: Sinks = NO_SINKS): string {
|
|
354
|
+
function emitRollback(t: string, buf: boolean, rawMode: number, s: Sinks = NO_SINKS): string {
|
|
297
355
|
// `_rbBuf` is the `_cstBuf` arm of `rollbackCstCaptureAt` plus the two trivia
|
|
298
356
|
// truncations, with the sink discrimination the label already answered removed.
|
|
299
357
|
// It takes no piece, so the header's sharing rule admits it.
|
|
@@ -301,14 +359,15 @@ function emitRollback(t: string, buf: boolean, s: Sinks = NO_SINKS): string {
|
|
|
301
359
|
// The two side sinks stay OUT of `_rbBuf` rather than growing its parameter
|
|
302
360
|
// list: they are absent from most tables, and a fixed seven-argument helper
|
|
303
361
|
// would make every grammar pay the two extra pushes to serve the ones that
|
|
304
|
-
// carry a field.
|
|
305
|
-
//
|
|
306
|
-
// sentinel, and `_fields` on the array alone.
|
|
362
|
+
// carry a field. Both are emitted only from a defined mark; the generic
|
|
363
|
+
// helper applies the same `undefined` sentinel when a site cannot write one.
|
|
307
364
|
const fd = s.fd ? `\nif(ctx._fields!==undefined&&ctx._fields.length!==${t}fd)ctx._fields.length=${t}fd` : ''
|
|
308
365
|
const er = s.er ? `\nif(ctx._errors!==undefined&&ctx._errors.length!==${t}er)ctx._errors.length=${t}er` : ''
|
|
309
|
-
if (buf) return `
|
|
310
|
-
if (
|
|
311
|
-
return `
|
|
366
|
+
if (buf && rawMode === RAW_OMIT) return `_rbNoRawBuf(ctx,${t}raw,${t}tl,${t}lv,${t}lg,${t}rt)${fd}${er}`
|
|
367
|
+
if (buf && rawMode === RAW_CAPTURE) return `_rbBuf(ctx,${t}raw,${t}tl,${t}lv,${t}lg,${t}rt)${fd}${er}`
|
|
368
|
+
if (buf) return `rollbackTriviaAt(ctx,${t}raw,${t}tl,${t}lv,${s.fd ? `${t}fd` : 'undefined'},${s.er ? `${t}er` : 'undefined'},${t}lg,${t}rt)`
|
|
369
|
+
if (!s.fd && !s.er) return `if(${t}n)rollbackTriviaAt(ctx,${t}raw,${t}tl,${t}lv,undefined,undefined,${t}lg,${t}rt)`
|
|
370
|
+
return `if(${t}n){rollbackTriviaAt(ctx,${t}raw,${t}tl,${t}lv,${s.fd ? `${t}fd` : 'undefined'},${s.er ? `${t}er` : 'undefined'},${t}lg,${t}rt)}`
|
|
312
371
|
}
|
|
313
372
|
|
|
314
373
|
/**
|
|
@@ -447,6 +506,11 @@ export function rebuildPools(
|
|
|
447
506
|
* Throws `Unemittable` for any construct not lowered. It does NOT compile the
|
|
448
507
|
* text — `assemble.ts` does — so a refusal and a compile failure stay two
|
|
449
508
|
* distinguishable outcomes at the call site.
|
|
509
|
+
*
|
|
510
|
+
* `staticBuild` is true only when `emit.ts` embeds the returned source as an
|
|
511
|
+
* ordinary factory literal in a macro artifact. It permits macro-only code
|
|
512
|
+
* shaping without perturbing the runtime `compile()` emitter, whose generated
|
|
513
|
+
* parser must remain byte-identical when the optimization cannot affect it.
|
|
450
514
|
*/
|
|
451
515
|
export function emitAssemblySource(
|
|
452
516
|
t: ResolvedTable,
|
|
@@ -461,8 +525,9 @@ export function emitAssemblySource(
|
|
|
461
525
|
probe: boolean
|
|
462
526
|
},
|
|
463
527
|
extraIps: readonly number[] = [],
|
|
528
|
+
staticBuild = false,
|
|
464
529
|
): EmitResult {
|
|
465
|
-
const { code, k, disp, dsp, triviaLabelled } = t
|
|
530
|
+
const { code, k, fx, disp, dsp, triviaLabelled } = t
|
|
466
531
|
const swapLegal = !cfg.trackLines
|
|
467
532
|
const hostCst = cfg.hostCst
|
|
468
533
|
|
|
@@ -498,23 +563,108 @@ export function emitAssemblySource(
|
|
|
498
563
|
// pool's `extraIps` — and each starts at `TOP`, because a caller outside the
|
|
499
564
|
// emitted scope supplies a context this pass cannot see.
|
|
500
565
|
const roots = [...Object.values(prog.rules), ...extraIps]
|
|
566
|
+
const reachable = [...reachableSites(code, roots)]
|
|
501
567
|
const labels = computeSiteLabels(code, roots, hostCst)
|
|
502
568
|
// Eligibility is pooled by the terminal's existing constant operand, matching
|
|
503
569
|
// the closure recognizer pool: distinct terminal rows sharing one spec share
|
|
504
570
|
// both the recognizer and the ordinary-terminal lowering.
|
|
505
571
|
const scalarSpecs = new Set<number>()
|
|
572
|
+
// Large choices are different: admitting every wrapped scalar would add a
|
|
573
|
+
// recognizer call to many arms whose one-character gate already did all the
|
|
574
|
+
// useful work. A multi-character literal can still refine an overlapping
|
|
575
|
+
// first-character mask (`@@` beside `@name`) without changing the ordinary
|
|
576
|
+
// terminal's inline lowering, so keep that narrower inventory separate.
|
|
577
|
+
const largeChoiceScalarSpecs = new Set<number>()
|
|
578
|
+
const largeChoiceLiteralFamilies = new Map<number, readonly number[]>()
|
|
579
|
+
const largeChoiceScalarSequences = new Map<number, LeadingScalarSequence>()
|
|
580
|
+
type TokenChoiceCandidate = { readonly arm: number; readonly dispatchIp: number }
|
|
581
|
+
const tokenChoiceCandidates = new Map<number, TokenChoiceCandidate>()
|
|
582
|
+
const tokenChoiceDispatches = new Set<number>()
|
|
583
|
+
const tokenChoiceBodies = new Set<number>()
|
|
506
584
|
if (!hostCst && !cfg.tolerant && !cfg.probe && !cfg.coverage && !cfg.trackLines) {
|
|
507
|
-
for (const ip of
|
|
508
|
-
if (code[ip]
|
|
509
|
-
|
|
510
|
-
|
|
585
|
+
for (const ip of reachable) {
|
|
586
|
+
if (code[ip] === OP_NODE) {
|
|
587
|
+
const child = scalarTerminalNodeChild(code, ip)
|
|
588
|
+
if (child >= 0) scalarSpecs.add(code[child + 1]!)
|
|
589
|
+
continue
|
|
590
|
+
}
|
|
591
|
+
if (code[ip] !== OP_CHOICE || disp[code[ip + 1]!]!.exclusive) continue
|
|
592
|
+
const n = code[ip + 2]!
|
|
593
|
+
for (let i = 0; i < n; i++) {
|
|
594
|
+
const armIp = code[ip + 4 + i]!
|
|
595
|
+
const child = leadingScalarTerminal(code, armIp, 2, true, true)
|
|
596
|
+
if (n === 2 || n === 3) {
|
|
597
|
+
if (child >= 0) scalarSpecs.add(code[child + 1]!)
|
|
598
|
+
continue
|
|
599
|
+
}
|
|
600
|
+
if (child >= 0) {
|
|
601
|
+
const spec = k[code[child + 1]!]
|
|
602
|
+
if (code[child] === OP_LIT && typeof spec === 'string' && spec.length >= 2) {
|
|
603
|
+
largeChoiceScalarSpecs.add(code[child + 1]!)
|
|
604
|
+
continue
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
const family = leadingLiteralFamily(code, k, armIp)
|
|
608
|
+
if (family !== undefined && family.length >= 2 && family.every(terminal => {
|
|
609
|
+
const value = k[code[terminal + 1]!]
|
|
610
|
+
return typeof value === 'string' && value.length >= 2
|
|
611
|
+
})) {
|
|
612
|
+
largeChoiceLiteralFamilies.set(armIp, family)
|
|
613
|
+
}
|
|
614
|
+
const sequence = leadingScalarSequence(code, armIp)
|
|
615
|
+
// A labelled trivia scanner is still recognition authority here. This
|
|
616
|
+
// pretest is pure: on a miss the ordinary arm's trivia log would have
|
|
617
|
+
// been rolled back, while on a hit the arm runs and records it normally.
|
|
618
|
+
if (sequence !== undefined && (sequence.trivia < 0
|
|
619
|
+
|| (swapLegal && t.triviaScan[sequence.trivia] != null))) {
|
|
620
|
+
largeChoiceScalarSequences.set(armIp, sequence)
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// A direct value-only transform around a dispatch cannot consume, branch,
|
|
625
|
+
// publish, or call author code before the dispatch selector. When that
|
|
626
|
+
// selector is one compiler-selected lexical body, the emitted choice may
|
|
627
|
+
// recognize and classify it before entering the arm, then hand the exact
|
|
628
|
+
// packed range and route to the ordinary LEX_BODY/DISPATCH readers. One
|
|
629
|
+
// candidate per ordered choice keeps source order authoritative: a second
|
|
630
|
+
// eligible arm leaves the whole site on the established PEG path.
|
|
631
|
+
let candidate: TokenChoiceCandidate | undefined
|
|
632
|
+
let ambiguous = false
|
|
633
|
+
for (let i = 0; i < n; i++) {
|
|
634
|
+
const armIp = code[ip + 4 + i]!
|
|
635
|
+
if (code[armIp] !== OP_XFORM) continue
|
|
636
|
+
const dispatchIp = code[armIp + 2]!
|
|
637
|
+
if (code[dispatchIp] !== OP_DISPATCH) continue
|
|
638
|
+
const selectorIp = code[dispatchIp + 1]!
|
|
639
|
+
if (code[selectorIp] !== OP_LEX_BODY) continue
|
|
640
|
+
if (candidate !== undefined) { ambiguous = true; break }
|
|
641
|
+
candidate = { arm: i, dispatchIp }
|
|
642
|
+
}
|
|
643
|
+
if (!ambiguous && candidate !== undefined) {
|
|
644
|
+
tokenChoiceCandidates.set(ip, candidate)
|
|
645
|
+
tokenChoiceDispatches.add(candidate.dispatchIp)
|
|
646
|
+
tokenChoiceBodies.add(code[code[candidate.dispatchIp + 1]! + 1]!)
|
|
647
|
+
}
|
|
511
648
|
}
|
|
512
649
|
}
|
|
513
650
|
|
|
514
|
-
//
|
|
515
|
-
//
|
|
516
|
-
//
|
|
517
|
-
//
|
|
651
|
+
// Helpers are referenced by DESCENDANT publication/rollback sites, not only
|
|
652
|
+
// by the node row that opened the count-only buffer. A specialized no-raw node
|
|
653
|
+
// can contain a shared child whose merged label is unknown even though this
|
|
654
|
+
// occurrence emits `_pushNodeNoRawBuf`; the node flag is the conservative
|
|
655
|
+
// factory-level authority for including the tiny prelude.
|
|
656
|
+
const needsNoRawPrelude = !hostCst && reachable.some(ip => {
|
|
657
|
+
const op = code[ip]
|
|
658
|
+
return (op === OP_NODE || op === OP_NODE_TRACK)
|
|
659
|
+
&& code[ip + 1]! >= 0 && code[ip + 4]! < 0 && (code[ip + 3]! & 2) !== 0
|
|
660
|
+
})
|
|
661
|
+
|
|
662
|
+
// THE SIDE-SINK FIXPOINT, over the same graph the labels walk. `OP_FIELD` is
|
|
663
|
+
// the only direct `_fields` writer and `OP_EXPECT` the only direct `_errors`
|
|
664
|
+
// writer. A tolerant repetition can also write `_errors` through recoverScan.
|
|
665
|
+
// Propagating those two bits through the child graph gives every speculative
|
|
666
|
+
// site the exact side sinks its subtree can mutate, including through cyclic
|
|
667
|
+
// OP_RULE edges.
|
|
518
668
|
//
|
|
519
669
|
// `OP_SCAN` RAISES BOTH, and that is not caution — it is a measured defect.
|
|
520
670
|
// The row runs an INTERPRETED combinator (`scanTo`/`balanced`, rebuilt by
|
|
@@ -528,26 +678,34 @@ export function emitAssemblySource(
|
|
|
528
678
|
//
|
|
529
679
|
// Any future row whose child is not an offset in `code` belongs here for the
|
|
530
680
|
// same reason. `OP_LIVE` is the other such row, and it is unemittable.
|
|
531
|
-
const
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
681
|
+
const sinkSites = reachable
|
|
682
|
+
const sinkBits = new Map<number, number>()
|
|
683
|
+
for (const ip of sinkSites) {
|
|
684
|
+
const op = code[ip]
|
|
685
|
+
sinkBits.set(ip,
|
|
686
|
+
op === OP_SCAN ? 3
|
|
687
|
+
: (op === OP_FIELD ? 1 : 0)
|
|
688
|
+
| (op === OP_EXPECT || (REC && (op === OP_REP || op === OP_REPV)) ? 2 : 0))
|
|
689
|
+
}
|
|
690
|
+
const sinkKids: number[] = []
|
|
691
|
+
let sinkChanged = true
|
|
692
|
+
while (sinkChanged) {
|
|
693
|
+
sinkChanged = false
|
|
694
|
+
for (const ip of sinkSites) {
|
|
695
|
+
let bits = sinkBits.get(ip) ?? 0
|
|
696
|
+
sinkKids.length = 0
|
|
697
|
+
childSlots(code, ip, sinkKids)
|
|
698
|
+
for (let i = 0; i < sinkKids.length; i++) bits |= sinkBits.get(sinkKids[i]!) ?? 0
|
|
699
|
+
if (bits !== sinkBits.get(ip)) {
|
|
700
|
+
sinkBits.set(ip, bits)
|
|
701
|
+
sinkChanged = true
|
|
702
|
+
}
|
|
548
703
|
}
|
|
549
|
-
|
|
550
|
-
|
|
704
|
+
}
|
|
705
|
+
const sinksAt = (ip: number): Sinks => {
|
|
706
|
+
const bits = sinkBits.get(ip) ?? 0
|
|
707
|
+
return bits === 0 ? NO_SINKS : { fd: (bits & 1) !== 0, er: (bits & 2) !== 0 }
|
|
708
|
+
}
|
|
551
709
|
|
|
552
710
|
const bodies: string[] = []
|
|
553
711
|
const byIp = new Map<number, string>()
|
|
@@ -584,6 +742,27 @@ export function emitAssemblySource(
|
|
|
584
742
|
const fxRef = (i: number): string => hoist('fx', `FX[${i}]`)
|
|
585
743
|
const fnRef = (i: number): string => hoist('fn', `FNS[${i}]`)
|
|
586
744
|
const recognizerRef = (i: number): string => hoist('rec', `RECOG[${i}]`)
|
|
745
|
+
type ScalarSequenceRef = { readonly name: string; readonly expected: readonly string[] }
|
|
746
|
+
const scalarSequenceRefs = new Map<string, ScalarSequenceRef>()
|
|
747
|
+
const scalarSequenceRef = (sequence: LeadingScalarSequence): ScalarSequenceRef => {
|
|
748
|
+
const key = `${sequence.trivia}:${sequence.terminals.map(terminal => code[terminal + 1]!).join(',')}`
|
|
749
|
+
const prior = scalarSequenceRefs.get(key)
|
|
750
|
+
if (prior !== undefined) return prior
|
|
751
|
+
const recognizers = sequence.terminals.map(terminal => recognizerRef(code[terminal + 1]!))
|
|
752
|
+
const expected = sequence.terminals.map(terminal => fxRef(code[terminal + 2]!))
|
|
753
|
+
const scan = sequence.trivia < 0 ? undefined : hoist('ts', `TRIVIASCAN[${sequence.trivia}]`)
|
|
754
|
+
const name = `_sd${scalarSequenceRefs.size}_`
|
|
755
|
+
const steps = recognizers.map((recognize, i) => `${i === 0 ? 'let ' : ''}at=${i === 0 ? 'pos' : scan === undefined ? 'cur' : `${scan}(input,cur)`}
|
|
756
|
+
${i === 0 ? 'let ' : ''}cur=${recognize}(input,at)
|
|
757
|
+
if(cur<0){_pfSeqEnd=at;return ${~i}}`).join('\n')
|
|
758
|
+
choiceDefs.push(`function ${name}(input,pos){
|
|
759
|
+
${steps}
|
|
760
|
+
return cur
|
|
761
|
+
}`)
|
|
762
|
+
const made = { name, expected }
|
|
763
|
+
scalarSequenceRefs.set(key, made)
|
|
764
|
+
return made
|
|
765
|
+
}
|
|
587
766
|
/**
|
|
588
767
|
* The sync sentinel for a char-class index, hoisted once per class.
|
|
589
768
|
*
|
|
@@ -597,6 +776,105 @@ export function emitAssemblySource(
|
|
|
597
776
|
let uid = 0
|
|
598
777
|
const tmp = (): string => `_t${uid++}_`
|
|
599
778
|
|
|
779
|
+
/** One dispatch matcher as source, shared by the ordinary and token-first paths. */
|
|
780
|
+
const dispatchClaim = (m: readonly [number, string, string, number]): string => {
|
|
781
|
+
switch (m[0]) {
|
|
782
|
+
case 0: return `key.startsWith(${q(m[1])})`
|
|
783
|
+
case 1: return `key.endsWith(${q(m[1])})`
|
|
784
|
+
case 3: return `asciiFoldKey(key).startsWith(${q(m[1])})`
|
|
785
|
+
case 4: return `asciiFoldKey(key).endsWith(${q(m[1])})`
|
|
786
|
+
default: {
|
|
787
|
+
if (!m[2].includes('g') && !m[2].includes('y')) {
|
|
788
|
+
try {
|
|
789
|
+
// Validate without changing malformed-row error timing.
|
|
790
|
+
new RegExp(m[1], m[2])
|
|
791
|
+
return `${hoist('dm', `new RegExp(${q(m[1])},${q(m[2])})`)}.test(key)`
|
|
792
|
+
} catch {}
|
|
793
|
+
}
|
|
794
|
+
return `new RegExp(${q(m[1])},${q(m[2])}).test(key)`
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
type TokenDecisionRef = { readonly name: string; readonly expected: string }
|
|
800
|
+
const tokenDecisionRefs = new Map<number, TokenDecisionRef>()
|
|
801
|
+
function tokenDecisionFor(dispatchIp: number): TokenDecisionRef {
|
|
802
|
+
const prior = tokenDecisionRefs.get(dispatchIp)
|
|
803
|
+
if (prior !== undefined) return prior
|
|
804
|
+
const selectorIp = code[dispatchIp + 1]!
|
|
805
|
+
const body = code[selectorIp + 1]!
|
|
806
|
+
const lineFlags = code[selectorIp + 4]!
|
|
807
|
+
const hasSuffix = (lineFlags & 4) !== 0
|
|
808
|
+
const di = code[dispatchIp + 2]!
|
|
809
|
+
const spec = dsp[di]!
|
|
810
|
+
const n = code[dispatchIp + 5]!
|
|
811
|
+
validateDispatchSpec(spec, n, code[dispatchIp + 4]!)
|
|
812
|
+
const recognize = hoist('lex', `LEX[${body}]`)
|
|
813
|
+
const bk = hoist('bk', `DSP[${di}].byKey`)
|
|
814
|
+
const expected = hoist('dx', `DSP[${di}].expected`)
|
|
815
|
+
const fold = spec.byFold.size > 0
|
|
816
|
+
? `if(arm===undefined)arm=${hoist('bf', `DSP[${di}].byFold`)}.get(asciiFoldKey(key))\n`
|
|
817
|
+
: ''
|
|
818
|
+
const chain = spec.match.length === 0
|
|
819
|
+
? ''
|
|
820
|
+
: `if(arm===undefined){\n${spec.match.map((m, i) => `${i === 0 ? '' : 'else '}if(${dispatchClaim(m)})arm=${m[3]}`).join('\n')}\n}\n`
|
|
821
|
+
const foldedEntries = [...spec.byFold.entries()]
|
|
822
|
+
// Exact, fail-closed shape of Jess Less's function-token dispatch. Keep the
|
|
823
|
+
// source+flags check explicit: changing that grammar matcher must disable
|
|
824
|
+
// this hand-lowered classifier until its url(/calc( exclusions are reviewed.
|
|
825
|
+
const fixedFunctionChoice = spec.byKey.size === 0
|
|
826
|
+
&& foldedEntries.length === 1
|
|
827
|
+
&& spec.match.length === 1
|
|
828
|
+
&& spec.match[0]![0] === 2
|
|
829
|
+
&& spec.match[0]![1] === '^(?!(?:url|calc)\\($).+\\($'
|
|
830
|
+
&& spec.match[0]![2] === 'i'
|
|
831
|
+
&& code[dispatchIp + 3]! < 0
|
|
832
|
+
const foldedRangeEquals = (value: string): string => {
|
|
833
|
+
const folded = value.replace(/[A-Z]/g, c => c.toLowerCase())
|
|
834
|
+
return `e-pos===${folded.length}&&${[...folded].map((c, i) => {
|
|
835
|
+
const cc = c.charCodeAt(0)
|
|
836
|
+
return `_asciiFoldCode(input.charCodeAt(pos+${i}))===${cc}`
|
|
837
|
+
}).join('&&')}`
|
|
838
|
+
}
|
|
839
|
+
const classify = fixedFunctionChoice
|
|
840
|
+
? (() => {
|
|
841
|
+
const [exact, exactArm] = foldedEntries[0]!
|
|
842
|
+
const genericArm = spec.match[0]![3]
|
|
843
|
+
return `let arm
|
|
844
|
+
if(${foldedRangeEquals(exact)})arm=${exactArm}
|
|
845
|
+
else if(sm&&e>pos+1){
|
|
846
|
+
let clean=true
|
|
847
|
+
for(let i=pos;i<e;i++){const c=input.charCodeAt(i);if(c===10||c===13||c===0x2028||c===0x2029){clean=false;break}}
|
|
848
|
+
if(clean&&!(${foldedRangeEquals('url(')})&&!(${foldedRangeEquals('calc(')}))arm=${genericArm}
|
|
849
|
+
}`
|
|
850
|
+
})()
|
|
851
|
+
: `const key=input.slice(pos,e)
|
|
852
|
+
let arm=${bk}.get(key)
|
|
853
|
+
${fold}${chain}`
|
|
854
|
+
const name = `_td${tokenDecisionRefs.size}_`
|
|
855
|
+
choiceDefs.push(`function ${name}(input,pos){
|
|
856
|
+
const r=${recognize}(input,pos)
|
|
857
|
+
if(r<0)return -1
|
|
858
|
+
const sm=${hasSuffix ? 'r%2===1' : 'false'},e=(r-(sm?1:0))/2
|
|
859
|
+
${classify}
|
|
860
|
+
if(arm===undefined){
|
|
861
|
+
${code[dispatchIp + 3]! < 0 ? '_pfTokEnd=e;return 0' : `arm=${n}`}
|
|
862
|
+
}
|
|
863
|
+
${fixedFunctionChoice ? 'const key=input.slice(pos,e)' : ''}
|
|
864
|
+
_pfTokInput=input
|
|
865
|
+
_pfTokPos=pos
|
|
866
|
+
_pfTokBody=${body}
|
|
867
|
+
_pfTokPacked=r
|
|
868
|
+
_pfTokValue=key
|
|
869
|
+
_pfTokDispatch=${dispatchIp}
|
|
870
|
+
_pfTokArm=arm
|
|
871
|
+
return 1
|
|
872
|
+
}`)
|
|
873
|
+
const made = { name, expected }
|
|
874
|
+
tokenDecisionRefs.set(dispatchIp, made)
|
|
875
|
+
return made
|
|
876
|
+
}
|
|
877
|
+
|
|
600
878
|
/**
|
|
601
879
|
* THE TRIVIA SCAN FOR ONE SITE LABEL.
|
|
602
880
|
*
|
|
@@ -796,6 +1074,7 @@ ${cfg.probe ? `failAt(ctx,${xf},pos)\n` : ''}return FAIL
|
|
|
796
1074
|
const op = code[ip]
|
|
797
1075
|
const head = `function ${fname}(input,pos,ctx){`
|
|
798
1076
|
const L = labels.at(ip)
|
|
1077
|
+
const sinks = sinksAt(ip)
|
|
799
1078
|
/**
|
|
800
1079
|
* THE LEAF CAPTURE TEST, and only the test.
|
|
801
1080
|
*
|
|
@@ -813,7 +1092,9 @@ ${cfg.probe ? `failAt(ctx,${xf},pos)\n` : ''}return FAIL
|
|
|
813
1092
|
* `trackLines` is `RunCfg`, and an open `_cstBuf` is the label. `_pushLeafBuf`
|
|
814
1093
|
* is that pair with both branches taken, and it takes no piece.
|
|
815
1094
|
*/
|
|
816
|
-
const pushLeaf = L.buf && !cfg.trackLines
|
|
1095
|
+
const pushLeaf = L.buf && !cfg.trackLines
|
|
1096
|
+
? L.raw === RAW_OMIT ? '_pushLeafNoRawBuf' : L.raw === RAW_CAPTURE ? '_pushLeafBuf' : '_pushLeaf'
|
|
1097
|
+
: '_pushLeaf'
|
|
817
1098
|
const captureLeaf = (value: string): string => {
|
|
818
1099
|
const call = `${pushLeaf}(ctx,${value},pos,e)`
|
|
819
1100
|
return L.buf ? call : `if(ctx._cstBuf!==undefined||ctx._cstLeaves!==undefined)${call}`
|
|
@@ -1173,20 +1454,24 @@ return out
|
|
|
1173
1454
|
}
|
|
1174
1455
|
|
|
1175
1456
|
case OP_LEX_BODY: {
|
|
1176
|
-
const
|
|
1457
|
+
const body = code[ip + 1]!
|
|
1458
|
+
const recognize = hoist('lex', `LEX[${body}]`)
|
|
1177
1459
|
const expected = fxRef(code[ip + 2]!)
|
|
1178
1460
|
const suffixExpected = fxRef(code[ip + 3]!)
|
|
1179
1461
|
const lineFlags = code[ip + 4]!
|
|
1180
1462
|
const hasSuffix = (lineFlags & 4) !== 0
|
|
1463
|
+
const pending = tokenChoiceBodies.has(body)
|
|
1181
1464
|
return `${head}
|
|
1182
|
-
const
|
|
1465
|
+
${pending ? `const tp=_pfTokBody===${body}&&_pfTokInput===input&&_pfTokPos===pos
|
|
1466
|
+
const r=tp?_pfTokPacked:${recognize}(input,pos)` : `const r=${recognize}(input,pos)`}
|
|
1183
1467
|
if(r<0){ctx._fe=pos;ctx._fx=${expected};if(ctx._probe!==undefined)failAt(ctx,${expected},pos);return FAIL}
|
|
1184
1468
|
const sm=${hasSuffix ? 'r%2===1' : 'false'},e=(r-(sm?1:0))/2
|
|
1185
1469
|
${(lineFlags & 1) !== 0 ? '_trackLines(ctx,input,sm?e-1:e)' : ''}
|
|
1186
1470
|
${hasSuffix ? 'ctx._fc=false' : ''}
|
|
1187
1471
|
${hasSuffix && (lineFlags & 2) !== 0 ? 'if(sm)_trackLines(ctx,input,e)' : ''}
|
|
1188
1472
|
${hasSuffix ? `if(!sm){ctx._fe=e;ctx._fx=${suffixExpected};if(ctx._probe!==undefined)failAt(ctx,${suffixExpected},e)}` : ''}
|
|
1189
|
-
const v
|
|
1473
|
+
const v=${pending ? 'tp?_pfTokValue:' : ''}input.slice(pos,e)
|
|
1474
|
+
${pending ? 'if(tp){_pfTokBody=-1;_pfTokValue=undefined}' : ''}
|
|
1190
1475
|
if(ctx._cstBuf!==undefined||ctx._cstLeaves!==undefined)pushCstLeaf(ctx,{_tag:'leaf',value:v,span:{start:pos,end:e}})
|
|
1191
1476
|
EC.e=e
|
|
1192
1477
|
return v
|
|
@@ -1245,12 +1530,13 @@ return v
|
|
|
1245
1530
|
|
|
1246
1531
|
case OP_ATTEMPT: {
|
|
1247
1532
|
const child = link(code[ip + 1]!)
|
|
1533
|
+
const clean = !REC && failureRollbackClean(prog, ip)
|
|
1248
1534
|
const p = tmp()
|
|
1249
1535
|
return `${head}
|
|
1250
|
-
${emitMark(p, L.buf, sinks)}
|
|
1536
|
+
${clean ? '' : emitMark(p, L.buf, L.raw, sinks)}
|
|
1251
1537
|
const v=${child}(input,pos,ctx)
|
|
1252
1538
|
if(v!==FAIL)return v
|
|
1253
|
-
${emitRollback(p, L.buf, sinks)}
|
|
1539
|
+
${clean ? '' : emitRollback(p, L.buf, L.raw, sinks)}
|
|
1254
1540
|
if(ctx._fc===true)return FAIL
|
|
1255
1541
|
ctx._fe=pos
|
|
1256
1542
|
return FAIL
|
|
@@ -1274,9 +1560,9 @@ return FAIL
|
|
|
1274
1560
|
const xf = fxRef(code[ip + 2]!)
|
|
1275
1561
|
const p = tmp()
|
|
1276
1562
|
return `${head}
|
|
1277
|
-
${emitMark(p, L.buf, sinks)}
|
|
1563
|
+
${emitMark(p, L.buf, L.raw, sinks)}
|
|
1278
1564
|
const v=${child}(input,pos,ctx)
|
|
1279
|
-
${emitRollback(p, L.buf, sinks)}
|
|
1565
|
+
${emitRollback(p, L.buf, L.raw, sinks)}
|
|
1280
1566
|
if(v===FAIL){EC.e=pos;return null}
|
|
1281
1567
|
ctx._fe=pos
|
|
1282
1568
|
ctx._fx=${xf}
|
|
@@ -1289,9 +1575,9 @@ return FAIL
|
|
|
1289
1575
|
const xf = fxRef(code[ip + 2]!)
|
|
1290
1576
|
const p = tmp()
|
|
1291
1577
|
return `${head}
|
|
1292
|
-
${emitMark(p, L.buf, sinks)}
|
|
1578
|
+
${emitMark(p, L.buf, L.raw, sinks)}
|
|
1293
1579
|
const v=${child}(input,pos,ctx)
|
|
1294
|
-
${emitRollback(p, L.buf, sinks)}
|
|
1580
|
+
${emitRollback(p, L.buf, L.raw, sinks)}
|
|
1295
1581
|
if(v===FAIL){ctx._fe=pos;ctx._fx=${xf};return FAIL}
|
|
1296
1582
|
EC.e=pos
|
|
1297
1583
|
return null
|
|
@@ -1300,14 +1586,15 @@ return null
|
|
|
1300
1586
|
|
|
1301
1587
|
case OP_OPT: {
|
|
1302
1588
|
const child = link(code[ip + 1]!)
|
|
1589
|
+
const clean = !REC && failureRollbackClean(prog, ip)
|
|
1303
1590
|
const p = tmp()
|
|
1304
1591
|
return `${head}
|
|
1305
|
-
${emitMark(p, L.buf, sinks)}
|
|
1592
|
+
${clean ? '' : emitMark(p, L.buf, L.raw, sinks)}
|
|
1306
1593
|
ctx._fc=false
|
|
1307
1594
|
const v=${child}(input,pos,ctx)
|
|
1308
1595
|
if(v===FAIL){
|
|
1309
1596
|
if(ctx._fc===true)return FAIL
|
|
1310
|
-
${emitRollback(p, L.buf, sinks)}
|
|
1597
|
+
${clean ? '' : emitRollback(p, L.buf, L.raw, sinks)}
|
|
1311
1598
|
EC.e=pos
|
|
1312
1599
|
return null
|
|
1313
1600
|
}
|
|
@@ -1443,6 +1730,30 @@ return FAIL
|
|
|
1443
1730
|
const expected = Array.from({ length: n }, (_, i) => fxRef(code[base + n + i]!))
|
|
1444
1731
|
const gates = Array.from({ length: n }, (_, i) => table.armCls[i] ?? null)
|
|
1445
1732
|
const gateRefs = Array.from({ length: n }, (_, i) => hoist('g', `DISP[${di}].armCls[${i}]`))
|
|
1733
|
+
type ChoicePretest = {
|
|
1734
|
+
readonly scalar?: readonly string[]
|
|
1735
|
+
readonly sequence?: ScalarSequenceRef
|
|
1736
|
+
readonly token?: TokenDecisionRef
|
|
1737
|
+
}
|
|
1738
|
+
const tokenCandidate = tokenChoiceCandidates.get(ip)
|
|
1739
|
+
const pretests = Array.from({ length: n }, (_, i): ChoicePretest | undefined => {
|
|
1740
|
+
if (tokenCandidate?.arm === i) {
|
|
1741
|
+
return { token: tokenDecisionFor(tokenCandidate.dispatchIp) }
|
|
1742
|
+
}
|
|
1743
|
+
const terminal = leadingScalarTerminal(code, code[base + i]!, 2, true, true)
|
|
1744
|
+
if (terminal >= 0) {
|
|
1745
|
+
const spec = code[terminal + 1]!
|
|
1746
|
+
if ((n === 2 || n === 3 ? scalarSpecs : largeChoiceScalarSpecs).has(spec)) {
|
|
1747
|
+
return { scalar: [recognizerRef(spec)] }
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
const family = largeChoiceLiteralFamilies.get(code[base + i]!)
|
|
1751
|
+
if (family !== undefined) {
|
|
1752
|
+
return { scalar: family.map(member => recognizerRef(code[member + 1]!)) }
|
|
1753
|
+
}
|
|
1754
|
+
const sequence = largeChoiceScalarSequences.get(code[base + i]!)
|
|
1755
|
+
return sequence === undefined ? undefined : { sequence: scalarSequenceRef(sequence) }
|
|
1756
|
+
})
|
|
1446
1757
|
const maskable = n <= 32
|
|
1447
1758
|
const maskName = maskable
|
|
1448
1759
|
? hoist('mk', `MASK[${masks.push(maskForClassRow(gates)) - 1}]`)
|
|
@@ -1452,53 +1763,222 @@ return FAIL
|
|
|
1452
1763
|
// cannot carry a second mutable answer for the arms' classes.
|
|
1453
1764
|
if (maskable) maskPlan.push(~di)
|
|
1454
1765
|
const p = tmp()
|
|
1455
|
-
|
|
1456
|
-
|
|
1766
|
+
// This proof lives only on the compiler-created program while the
|
|
1767
|
+
// precompiled assembly is printed. A hand-built or deserialized table
|
|
1768
|
+
// has no authority and therefore keeps the established all-arm path.
|
|
1769
|
+
const encodedRollbackMask = choiceRollbackMask(prog, ip) ?? -1
|
|
1770
|
+
const rollbackMask = REC ? -1 : encodedRollbackMask
|
|
1771
|
+
const hasRollback = rollbackMask !== 0
|
|
1772
|
+
// An always-consuming leading scalar makes a failure at `pos`
|
|
1773
|
+
// statically exact: it is the arm's derived opener set, and the
|
|
1774
|
+
// choice's own `choiceFx` already concatenates every such set in
|
|
1775
|
+
// source order. Defer array merging until an arm reaches deeper input
|
|
1776
|
+
// so a later successful arm pays no diagnostic-allocation tax.
|
|
1777
|
+
//
|
|
1778
|
+
// attempt() is a hard boundary: it can fail deeper and deliberately
|
|
1779
|
+
// re-anchor `_fe` at `pos` while keeping the inner dynamic expected
|
|
1780
|
+
// set. A zero-width regex has the same ambiguity, so neither qualifies.
|
|
1781
|
+
const startFailureExact = arms.every((_arm, i) => {
|
|
1782
|
+
const terminal = leadingScalarTerminal(code, code[base + i]!, 0, false)
|
|
1783
|
+
if (terminal < 0) return false
|
|
1784
|
+
const op = code[terminal]
|
|
1785
|
+
const spec = k[code[terminal + 1]!]
|
|
1786
|
+
// Recognition shape is not diagnostic authority. word() is one
|
|
1787
|
+
// important counterexample: its terminal reports `keyword`, while
|
|
1788
|
+
// deriveExpected at an enclosing choice names the concrete word.
|
|
1789
|
+
// Substituting choiceFx for the dynamic terminal set is sound only
|
|
1790
|
+
// when both encoded authorities agree byte-for-byte, including
|
|
1791
|
+
// duplicates and source order.
|
|
1792
|
+
const terminalFx = fx[code[terminal + 2]!]!
|
|
1793
|
+
const armFx = fx[code[base + n + i]!]!
|
|
1794
|
+
if (terminalFx.length !== armFx.length
|
|
1795
|
+
|| terminalFx.some((expected, at) => expected !== armFx[at])) return false
|
|
1796
|
+
if (op === OP_LIT) return typeof spec === 'string' && spec.length > 0
|
|
1797
|
+
return op === OP_RX && spec instanceof RegExp && !regexCanMatchEmpty(spec.source)
|
|
1798
|
+
})
|
|
1799
|
+
const rollbackFor = (i: number): string =>
|
|
1800
|
+
rollbackMask === -1 || (rollbackMask & (1 << i)) !== 0
|
|
1801
|
+
? emitRollback(p, L.buf, L.raw, sinks)
|
|
1802
|
+
: ''
|
|
1803
|
+
if (choiceCannotCommit(prog, ip) && !startFailureExact) {
|
|
1804
|
+
// A successful ordered choice does not expose any losing arm's
|
|
1805
|
+
// diagnostic. Snapshot each entered arm's failure in scalar locals;
|
|
1806
|
+
// choose the deepest set and concatenate ties on the cold TOTAL-
|
|
1807
|
+
// failure exit. This is the direct-emitter topology from 0.45,
|
|
1808
|
+
// restored without changing the table wire format.
|
|
1809
|
+
const deepAt = Array.from({ length: n }, () => tmp())
|
|
1810
|
+
const deepFx = Array.from({ length: n }, () => tmp())
|
|
1811
|
+
const remember = (i: number, at: string, expectedSet: string): string =>
|
|
1812
|
+
`${deepAt[i]}=${at};${deepFx[i]}=${expectedSet}`
|
|
1813
|
+
const lazyArms = (ascii: boolean): string => arms.map((arm, i) => {
|
|
1814
|
+
const pretest = pretests[i]
|
|
1815
|
+
const decision = pretest?.token === undefined && pretest?.sequence === undefined ? '' : tmp()
|
|
1816
|
+
const scalarCondition = pretest?.scalar === undefined ? ''
|
|
1817
|
+
: pretest.scalar.length === 1
|
|
1818
|
+
? `&&${pretest.scalar[0]}(input,pos)>=0`
|
|
1819
|
+
: `&&(${pretest.scalar.map(recognize => `${recognize}(input,pos)>=0`).join('||')})`
|
|
1820
|
+
const sequenceCondition = pretest?.sequence === undefined ? ''
|
|
1821
|
+
: `&&(${decision}=${pretest.sequence.name}(input,pos))>=0`
|
|
1822
|
+
const condition = pretest?.token !== undefined
|
|
1823
|
+
? `&&(${decision}=${pretest.token.name}(input,pos))>0`
|
|
1824
|
+
: scalarCondition || sequenceCondition
|
|
1825
|
+
const gate = ascii
|
|
1826
|
+
? `(bits&${1 << i})!==0${condition}`
|
|
1827
|
+
: `(${gateRefs[i]}===null||classHas(${gateRefs[i]},c))${condition}`
|
|
1828
|
+
const routeMiss = pretest?.token === undefined ? '' : `
|
|
1829
|
+
if(${decision}===0){
|
|
1830
|
+
ctx._fc=false
|
|
1831
|
+
{const at=_pfTokEnd
|
|
1832
|
+
${remember(i, 'at', pretest.token.expected)}}
|
|
1833
|
+
}`
|
|
1834
|
+
const sequenceMiss = pretest?.sequence === undefined ? '' : `
|
|
1835
|
+
else if(${ascii ? `(bits&${1 << i})!==0&&` : ''}${decision}<0){
|
|
1836
|
+
ctx._fc=false
|
|
1837
|
+
{const at=_pfSeqEnd
|
|
1838
|
+
${remember(i, 'at', pretest.sequence.expected.map((value, at) => `${decision}===${~at}?${value}:`).join('') + pretest.sequence.expected.at(-1))}}
|
|
1839
|
+
}`
|
|
1840
|
+
return `${decision === '' ? '' : `let ${decision}=${pretest?.sequence === undefined ? -1 : 0}\n`}if(${gate}){
|
|
1841
|
+
ctx._fc=false
|
|
1842
|
+
{const v=${arm}(input,pos,ctx)
|
|
1843
|
+
if(v!==FAIL)return v}
|
|
1844
|
+
{const at=ctx._fe??pos
|
|
1845
|
+
${remember(i, 'at', 'ctx._fx')}}
|
|
1846
|
+
${rollbackFor(i)}
|
|
1847
|
+
}${routeMiss}${sequenceMiss}`
|
|
1848
|
+
}).join('\n')
|
|
1849
|
+
const merge = deepAt.map((at, i) => `{const at=${at}??pos
|
|
1850
|
+
const ex=${at}===undefined?${expected[i]}:${deepFx[i]}
|
|
1851
|
+
if(at>best){best=at;acc=_accSet(ex,undefined)}
|
|
1852
|
+
else if(at===best)acc=_accSet(ex,acc)
|
|
1853
|
+
}`).join('\n')
|
|
1854
|
+
const finish = `let best=pos,acc
|
|
1855
|
+
${merge}
|
|
1856
|
+
ctx._fe=pos;ctx._fx=acc??${choiceFx}
|
|
1857
|
+
return FAIL`
|
|
1858
|
+
return `${head}
|
|
1859
|
+
const c=lead(input,pos)
|
|
1860
|
+
${hasRollback ? emitMark(p, L.buf, L.raw, sinks) : ''}
|
|
1861
|
+
${deepAt.map((at, i) => `let ${at},${deepFx[i]}`).join('\n')}
|
|
1862
|
+
${maskable ? `if(c<128){
|
|
1863
|
+
const bits=${maskName}[c<0?128:c]
|
|
1864
|
+
if(bits===0){ctx._fe=pos;ctx._fx=${choiceFx};return FAIL}
|
|
1865
|
+
${lazyArms(true)}
|
|
1866
|
+
}else{
|
|
1867
|
+
${lazyArms(false)}
|
|
1868
|
+
}
|
|
1869
|
+
` : lazyArms(false)}
|
|
1870
|
+
${finish}
|
|
1871
|
+
}`
|
|
1872
|
+
}
|
|
1873
|
+
const needsCatch = maskable || startFailureExact
|
|
1874
|
+
const catchName = needsCatch ? `_cx${uid++}_` : ''
|
|
1875
|
+
if (needsCatch) {
|
|
1457
1876
|
const catchCases = expected.map((e, i) =>
|
|
1458
1877
|
`case ${i}:if(target<=${i})return acc;acc=_accSet(${e},acc)`).join('\n')
|
|
1459
1878
|
choiceDefs.push(`function ${catchName}(target,prev,acc){switch(prev){\n${catchCases}\n}return acc}`)
|
|
1460
1879
|
}
|
|
1461
1880
|
const maskArms = maskable ? arms.map((arm, i) => {
|
|
1462
|
-
|
|
1881
|
+
const pretest = pretests[i]
|
|
1882
|
+
const decision = pretest?.token === undefined && pretest?.sequence === undefined ? '' : tmp()
|
|
1883
|
+
const scalarCondition = pretest?.scalar === undefined ? ''
|
|
1884
|
+
: pretest.scalar.length === 1
|
|
1885
|
+
? `&&${pretest.scalar[0]}(input,pos)>=0`
|
|
1886
|
+
: `&&(${pretest.scalar.map(recognize => `${recognize}(input,pos)>=0`).join('||')})`
|
|
1887
|
+
const sequenceCondition = pretest?.sequence === undefined ? ''
|
|
1888
|
+
: `&&(${decision}=${pretest.sequence.name}(input,pos))>=0`
|
|
1889
|
+
const condition = pretest?.token !== undefined
|
|
1890
|
+
? `&&(${decision}=${pretest.token.name}(input,pos))>0`
|
|
1891
|
+
: scalarCondition || sequenceCondition
|
|
1892
|
+
const routeMiss = pretest?.token === undefined ? '' : `
|
|
1893
|
+
if(${decision}===0){
|
|
1894
|
+
ctx._fc=false
|
|
1895
|
+
${startFailureExact ? '' : `if(best===pos)acc=${catchName}(${i},prev,acc)
|
|
1896
|
+
prev=${i + 1}`}
|
|
1897
|
+
{const at=_pfTokEnd
|
|
1898
|
+
if(at>best){best=at;acc=undefined}
|
|
1899
|
+
if(at===best${startFailureExact ? '&&at>pos' : ''})acc=_accSet(${pretest.token.expected},acc)}
|
|
1900
|
+
}`
|
|
1901
|
+
const sequenceMiss = pretest?.sequence === undefined ? '' : `
|
|
1902
|
+
else if((bits&${1 << i})!==0&&${decision}<0){
|
|
1903
|
+
ctx._fc=false
|
|
1904
|
+
${startFailureExact ? '' : `if(best===pos)acc=${catchName}(${i},prev,acc)
|
|
1905
|
+
prev=${i + 1}`}
|
|
1906
|
+
{const at=_pfSeqEnd
|
|
1907
|
+
if(at>best){best=at;acc=undefined}
|
|
1908
|
+
if(at===best${startFailureExact ? '&&at>pos' : ''})acc=_accSet(${pretest.sequence.expected.map((value, at) => `${decision}===${~at}?${value}:`).join('')}${pretest.sequence.expected.at(-1)},acc)}
|
|
1909
|
+
}`
|
|
1910
|
+
return `${decision === '' ? '' : `let ${decision}=${pretest?.sequence === undefined ? -1 : 0}\n`}if((bits&${1 << i})!==0${condition}){
|
|
1463
1911
|
ctx._fc=false
|
|
1464
1912
|
{const v=${arm}(input,pos,ctx)
|
|
1465
1913
|
if(v!==FAIL)return v}
|
|
1466
|
-
if(best===pos)acc=${catchName}(${i},prev,acc)
|
|
1467
|
-
prev=${i + 1}
|
|
1914
|
+
${startFailureExact ? '' : `if(best===pos)acc=${catchName}(${i},prev,acc)
|
|
1915
|
+
prev=${i + 1}`}
|
|
1468
1916
|
{const at=ctx._fe??pos
|
|
1469
1917
|
if(at>best){best=at;acc=undefined}
|
|
1470
|
-
if(at===best)acc=_accSet(ctx._fx,acc)}
|
|
1471
|
-
if(ctx._fc===true){if(acc!==undefined)ctx._fx=acc;return FAIL}
|
|
1472
|
-
${
|
|
1473
|
-
}`
|
|
1918
|
+
if(at===best${startFailureExact ? '&&at>pos' : ''})acc=_accSet(ctx._fx,acc)}
|
|
1919
|
+
if(ctx._fc===true){${startFailureExact ? `if(best===pos){acc=${catchName}(${i},0,undefined);acc=_accSet(ctx._fx,acc)}` : ''}if(acc!==undefined)ctx._fx=acc;return FAIL}
|
|
1920
|
+
${rollbackFor(i)}
|
|
1921
|
+
}${routeMiss}${sequenceMiss}`
|
|
1474
1922
|
}).join('\n') : ''
|
|
1475
|
-
const generalArms = arms.map((arm, i) =>
|
|
1923
|
+
const generalArms = arms.map((arm, i) => {
|
|
1924
|
+
const pretest = pretests[i]
|
|
1925
|
+
const decision = pretest?.token === undefined && pretest?.sequence === undefined ? '' : tmp()
|
|
1926
|
+
const scalarCondition = pretest?.scalar === undefined ? ''
|
|
1927
|
+
: pretest.scalar.length === 1
|
|
1928
|
+
? `&&${pretest.scalar[0]}(input,pos)>=0`
|
|
1929
|
+
: `&&(${pretest.scalar.map(recognize => `${recognize}(input,pos)>=0`).join('||')})`
|
|
1930
|
+
const sequenceCondition = pretest?.sequence === undefined ? ''
|
|
1931
|
+
: `&&(${decision}=${pretest.sequence.name}(input,pos))>=0`
|
|
1932
|
+
const condition = pretest?.token !== undefined
|
|
1933
|
+
? `&&(${decision}=${pretest.token.name}(input,pos))>0`
|
|
1934
|
+
: scalarCondition || sequenceCondition
|
|
1935
|
+
const miss = pretest?.sequence !== undefined
|
|
1936
|
+
? `else if(${decision}<0){
|
|
1937
|
+
ctx._fc=false
|
|
1938
|
+
const at=_pfSeqEnd
|
|
1939
|
+
if(at>best){best=at;acc=undefined}
|
|
1940
|
+
if(at===best${startFailureExact ? '&&at>pos' : ''})acc=_accSet(${pretest.sequence.expected.map((value, at) => `${decision}===${~at}?${value}:`).join('')}${pretest.sequence.expected.at(-1)},acc)
|
|
1941
|
+
}${startFailureExact ? '' : `else if(best===pos)acc=_accSet(${expected[i]},acc)`}`
|
|
1942
|
+
: pretest?.token === undefined
|
|
1943
|
+
? startFailureExact ? '' : `else if(best===pos)acc=_accSet(${expected[i]},acc)`
|
|
1944
|
+
: `else if(${decision}===0){
|
|
1945
|
+
ctx._fc=false
|
|
1946
|
+
const at=_pfTokEnd
|
|
1947
|
+
if(at>best){best=at;acc=undefined}
|
|
1948
|
+
if(at===best${startFailureExact ? '&&at>pos' : ''})acc=_accSet(${pretest.token.expected},acc)
|
|
1949
|
+
}${startFailureExact ? '' : `else if(best===pos)acc=_accSet(${expected[i]},acc)`}`
|
|
1950
|
+
return `${decision === '' ? '' : `let ${decision}=${pretest?.sequence === undefined ? -1 : 0}\n`}if((${gateRefs[i]}===null||classHas(${gateRefs[i]},c))${condition}){
|
|
1476
1951
|
ctx._fc=false
|
|
1477
1952
|
{const v=${arm}(input,pos,ctx)
|
|
1478
1953
|
if(v!==FAIL)return v}
|
|
1479
1954
|
{const at=ctx._fe??pos
|
|
1480
1955
|
if(at>best){best=at;acc=undefined}
|
|
1481
|
-
if(at===best)acc=_accSet(ctx._fx,acc)}
|
|
1482
|
-
if(ctx._fc===true){if(acc!==undefined)ctx._fx=acc;return FAIL}
|
|
1483
|
-
${
|
|
1484
|
-
}
|
|
1956
|
+
if(at===best${startFailureExact ? '&&at>pos' : ''})acc=_accSet(ctx._fx,acc)}
|
|
1957
|
+
if(ctx._fc===true){${startFailureExact ? `if(best===pos){acc=${catchName}(${i},0,undefined);acc=_accSet(ctx._fx,acc)}` : ''}if(acc!==undefined)ctx._fx=acc;return FAIL}
|
|
1958
|
+
${rollbackFor(i)}
|
|
1959
|
+
}${miss}`
|
|
1960
|
+
}).join('\n')
|
|
1485
1961
|
|
|
1962
|
+
// A zero compatible-arm mask is stronger than a speculative miss: no
|
|
1963
|
+
// arm can run, so the choice's encoded flat expected set is already
|
|
1964
|
+
// the exact result. Do not walk the arm ladder merely to rebuild it.
|
|
1486
1965
|
return `${head}
|
|
1487
1966
|
const c=lead(input,pos)
|
|
1488
|
-
${emitMark(p, L.buf, sinks)}
|
|
1967
|
+
${hasRollback ? emitMark(p, L.buf, L.raw, sinks) : ''}
|
|
1489
1968
|
let acc
|
|
1490
1969
|
let best=pos
|
|
1491
1970
|
${maskable ? `if(c<128){
|
|
1492
1971
|
const bits=${maskName}[c<0?128:c]
|
|
1493
|
-
|
|
1972
|
+
if(bits===0){ctx._fe=pos;ctx._fx=${choiceFx};return FAIL}
|
|
1973
|
+
${startFailureExact ? '' : 'let prev=0'}
|
|
1494
1974
|
${maskArms}
|
|
1495
|
-
if(best===pos)acc=${catchName}(${n},prev,acc)
|
|
1496
|
-
ctx._fe=pos;ctx._fx
|
|
1975
|
+
${startFailureExact ? '' : `if(best===pos)acc=${catchName}(${n},prev,acc)`}
|
|
1976
|
+
ctx._fe=pos;ctx._fx=${startFailureExact ? `best===pos?${choiceFx}:acc??${choiceFx}` : `acc??${choiceFx}`}
|
|
1497
1977
|
return FAIL
|
|
1498
1978
|
}
|
|
1499
1979
|
` : ''}
|
|
1500
1980
|
${generalArms}
|
|
1501
|
-
ctx._fe=pos;ctx._fx
|
|
1981
|
+
ctx._fe=pos;ctx._fx=${startFailureExact ? `best===pos?${choiceFx}:acc??${choiceFx}` : `acc??${choiceFx}`}
|
|
1502
1982
|
return FAIL
|
|
1503
1983
|
}`
|
|
1504
1984
|
}
|
|
@@ -1519,44 +1999,17 @@ return FAIL
|
|
|
1519
1999
|
for (let i = 0; i < n; i++) arms.push(link(code[armBase + i]!))
|
|
1520
2000
|
const bk = hoist('bk', `DSP[${di}].byKey`)
|
|
1521
2001
|
const dx = hoist('dx', `DSP[${di}].expected`)
|
|
1522
|
-
// THE MATCHER ARMS, AS SOURCE. `exec.ts`'s `linkMatcher` mints one
|
|
1523
|
-
// closure per arm from four literals it already has in hand; the four
|
|
1524
|
-
// are TABLE DATA, so here they are the test itself and no closure, no
|
|
1525
|
-
// pool and no indexed call exist. Kinds are `matcherClaims`'s exactly
|
|
1526
|
-
// — 0/1 raw prefix/suffix, 3/4 the pre-folded pair, anything else a
|
|
1527
|
-
// regex. Public `matches()` refuses global/sticky patterns, so its
|
|
1528
|
-
// RegExp can be compiled once into the assembly prelude. Hand-built
|
|
1529
|
-
// low-level rows with g/y or invalid flags retain the old per-test
|
|
1530
|
-
// construction and therefore the old `lastIndex`/throw behavior.
|
|
1531
|
-
const claims = (m: readonly [number, string, string, number]): string => {
|
|
1532
|
-
switch (m[0]) {
|
|
1533
|
-
case 0: return `key.startsWith(${q(m[1])})`
|
|
1534
|
-
case 1: return `key.endsWith(${q(m[1])})`
|
|
1535
|
-
case 3: return `asciiFoldKey(key).startsWith(${q(m[1])})`
|
|
1536
|
-
case 4: return `asciiFoldKey(key).endsWith(${q(m[1])})`
|
|
1537
|
-
default: {
|
|
1538
|
-
if (!m[2].includes('g') && !m[2].includes('y')) {
|
|
1539
|
-
try {
|
|
1540
|
-
// Validate without changing malformed-row error timing.
|
|
1541
|
-
new RegExp(m[1], m[2])
|
|
1542
|
-
return `${hoist('dm', `new RegExp(${q(m[1])},${q(m[2])})`)}.test(key)`
|
|
1543
|
-
} catch {}
|
|
1544
|
-
}
|
|
1545
|
-
return `new RegExp(${q(m[1])},${q(m[2])}).test(key)`
|
|
1546
|
-
}
|
|
1547
|
-
}
|
|
1548
|
-
}
|
|
1549
2002
|
const chain = spec.match.length === 0
|
|
1550
2003
|
? ''
|
|
1551
|
-
: `if(arm===undefined){\n${spec.match.map((m, i) => `${i === 0 ? '' : 'else '}if(${
|
|
2004
|
+
: `if(arm===undefined){\n${spec.match.map((m, i) => `${i === 0 ? '' : 'else '}if(${dispatchClaim(m)})arm=${m[3]}`).join('\n')}\n}\n`
|
|
1552
2005
|
const fold = spec.byFold.size > 0
|
|
1553
2006
|
? `if(arm===undefined)arm=${hoist('bf', `DSP[${di}].byFold`)}.get(asciiFoldKey(key))\n`
|
|
1554
2007
|
: ''
|
|
1555
2008
|
const m1 = tmp()
|
|
1556
2009
|
const m2 = tmp()
|
|
1557
2010
|
const routedCall = (target: string): string => `{const savedRouted=ctx._routed
|
|
1558
|
-
${emitRollback(m1, L.buf, sinks)}
|
|
1559
|
-
${emitMark(m2, L.buf, sinks, false)}
|
|
2011
|
+
${emitRollback(m1, L.buf, L.raw, sinks)}
|
|
2012
|
+
${emitMark(m2, L.buf, L.raw, sinks, false)}
|
|
1560
2013
|
ctx._routed={value:key,span:{start:pos,end:selEnd}}
|
|
1561
2014
|
try{v=${target}(input,pos,ctx)}finally{ctx._routed=savedRouted}
|
|
1562
2015
|
break}`
|
|
@@ -1567,29 +2020,41 @@ break}`
|
|
|
1567
2020
|
const fallbackCase = other === undefined ? '' : `default:${otherRouted
|
|
1568
2021
|
? routedCall(other)
|
|
1569
2022
|
: plainCall(other)}`
|
|
2023
|
+
const armSelection = tokenChoiceDispatches.has(ip)
|
|
2024
|
+
? `const tp=_pfTokDispatch===${ip}&&_pfTokInput===input&&_pfTokPos===pos
|
|
2025
|
+
let arm
|
|
2026
|
+
if(tp){
|
|
2027
|
+
arm=_pfTokArm
|
|
2028
|
+
_pfTokDispatch=-1
|
|
2029
|
+
_pfTokInput=undefined
|
|
2030
|
+
if(arm===${n})arm=undefined
|
|
2031
|
+
}else{
|
|
2032
|
+
arm=${bk}.get(key)
|
|
2033
|
+
${fold}${chain}}`
|
|
2034
|
+
: `let arm=${bk}.get(key)
|
|
2035
|
+
${fold}${chain}`
|
|
1570
2036
|
// THE SELECTOR RUNS ONCE and the key it returns picks the arm — that is
|
|
1571
2037
|
// what `dispatch()` buys over a choice of arms that each re-parse the
|
|
1572
2038
|
// opener. A routed arm rewinds the selector's trivia capture and gets
|
|
1573
2039
|
// the token handed to it (`OP_ROUTED`) instead of re-matching it.
|
|
1574
2040
|
return `${head}
|
|
1575
|
-
${emitMark(m1, L.buf, sinks)}
|
|
2041
|
+
${emitMark(m1, L.buf, L.raw, sinks)}
|
|
1576
2042
|
const sv=${selector}(input,pos,ctx)
|
|
1577
2043
|
if(sv===FAIL)return FAIL
|
|
1578
2044
|
const selEnd=EC.e
|
|
1579
2045
|
const key=sv
|
|
1580
|
-
|
|
1581
|
-
${fold}${chain}
|
|
2046
|
+
${armSelection}
|
|
1582
2047
|
if(arm===undefined){
|
|
1583
2048
|
${other === undefined ? `ctx._fe=selEnd;ctx._fx=${dx};return FAIL` : ''}
|
|
1584
2049
|
}
|
|
1585
|
-
${emitMark(m2, L.buf, sinks)}
|
|
2050
|
+
${emitMark(m2, L.buf, L.raw, sinks)}
|
|
1586
2051
|
let v
|
|
1587
2052
|
switch(arm){
|
|
1588
2053
|
${armCases}
|
|
1589
2054
|
${fallbackCase}
|
|
1590
2055
|
}
|
|
1591
2056
|
if(v===FAIL){
|
|
1592
|
-
${emitRollback(m2, L.buf, sinks)}
|
|
2057
|
+
${emitRollback(m2, L.buf, L.raw, sinks)}
|
|
1593
2058
|
ctx._fc=true
|
|
1594
2059
|
return FAIL
|
|
1595
2060
|
}
|
|
@@ -1643,15 +2108,23 @@ return [key,v]
|
|
|
1643
2108
|
// non-loop marks. Before `OP_FIELD` and `OP_EXPECT` were emittable the
|
|
1644
2109
|
// answer was "never"; it is now "per table", and a grammar with no field
|
|
1645
2110
|
// still pays neither the two loads nor the two stores per item.
|
|
1646
|
-
const pfd = sinks.fd ? `${p}fd` : '
|
|
2111
|
+
const pfd = sinks.fd ? `${p}fd` : 'undefined'
|
|
1647
2112
|
const per = sinks.er ? `${p}er` : 'undefined'
|
|
1648
|
-
const
|
|
1649
|
-
|
|
2113
|
+
const rbHelper = L.raw === RAW_OMIT ? '_rbNoRawBuf' : '_rbBuf'
|
|
2114
|
+
const rb = L.buf && L.raw !== 0
|
|
2115
|
+
? `${rbHelper}(ctx,${p}raw,${p}tl,${p}lv,${p}lg,${p}rt)${sinks.fd ? `\nif(ctx._fields!==undefined&&ctx._fields.length!==${p}fd)ctx._fields.length=${p}fd` : ''}${sinks.er ? `\nif(ctx._errors!==undefined&&ctx._errors.length!==${p}er)ctx._errors.length=${p}er` : ''}`
|
|
2116
|
+
: L.buf
|
|
2117
|
+
? `rollbackTriviaAt(ctx,${p}raw,${p}tl,${p}lv,${pfd},${per},${p}lg,${p}rt)`
|
|
1650
2118
|
: `if(needMark)rollbackTriviaAt(ctx,${p}raw,${p}tl,${p}lv,${pfd},${per},${p}lg,${p}rt)`
|
|
1651
2119
|
const markSinks = `${sinks.fd ? `\n${p}fd=ctx._fields!==undefined?ctx._fields.length:0` : ''}${sinks.er ? `\n${p}er=ctx._errors!==undefined?ctx._errors.length:0` : ''}`
|
|
2120
|
+
const markRaw = L.raw === RAW_OMIT
|
|
2121
|
+
? `${p}raw=b.rawLen`
|
|
2122
|
+
: L.raw === RAW_CAPTURE
|
|
2123
|
+
? `const r=b.raw;${p}raw=r!==undefined?r.length:b.rawSingle!==undefined?1:0`
|
|
2124
|
+
: `const r=b.raw;${p}raw=b.noRaw===true?b.rawLen:(r!==undefined?r.length:b.rawSingle!==undefined?1:0)`
|
|
1652
2125
|
const markBody = L.buf
|
|
1653
2126
|
? `const b=ctx._cstBuf
|
|
1654
|
-
|
|
2127
|
+
${markRaw}
|
|
1655
2128
|
const h=b.ch;${p}lv=h!==undefined?h.length:b.single!==undefined?1:0
|
|
1656
2129
|
const l=b.tl;${p}tl=l!==undefined?l.length:0
|
|
1657
2130
|
${p}lg=ctx._triviaLog!==undefined?ctx._triviaLog.length:0
|
|
@@ -1659,7 +2132,7 @@ ${p}rt=ctx._rootTriviaLog!==undefined?ctx._rootTriviaLog.length:0${markSinks}`
|
|
|
1659
2132
|
: `if(needMark){
|
|
1660
2133
|
const b=ctx._cstBuf
|
|
1661
2134
|
if(b!==undefined){
|
|
1662
|
-
const r=b.raw;${p}raw=r!==undefined?r.length:b.rawSingle!==undefined?1:0
|
|
2135
|
+
const r=b.raw;${p}raw=b.noRaw===true?b.rawLen:(r!==undefined?r.length:b.rawSingle!==undefined?1:0)
|
|
1663
2136
|
const h=b.ch;${p}lv=h!==undefined?h.length:b.single!==undefined?1:0
|
|
1664
2137
|
const l=b.tl;${p}tl=l!==undefined?l.length:0
|
|
1665
2138
|
}else{
|
|
@@ -1804,7 +2277,13 @@ const leaf={_tag:'leaf',value,span:{start:pos,end}}
|
|
|
1804
2277
|
const kids=[leaf],rawKids=[leaf],span={start:pos,end}
|
|
1805
2278
|
EC.e=end
|
|
1806
2279
|
const nd=${build}(kids,undefined,span,rawKids,EMPTY_TL,undefined)
|
|
1807
|
-
${L.buf
|
|
2280
|
+
${L.buf && L.raw === RAW_OMIT
|
|
2281
|
+
? '_pushNodeNoRawBuf(ctx,nd)'
|
|
2282
|
+
: L.buf && L.raw === RAW_CAPTURE
|
|
2283
|
+
? 'pushCstChild(ctx,nd,rawEntry(nd,input,pos,end))'
|
|
2284
|
+
: staticBuild
|
|
2285
|
+
? 'if(ctx._cstBuf!==undefined||ctx._cstChildren!==undefined)pushCstChild(ctx,nd,ctx._cstBuf!==undefined&&ctx._cstBuf.noRaw===true?undefined:ctx._cstBuf!==undefined||ctx._cstRawChildren!==undefined?rawEntry(nd,input,pos,end):undefined)'
|
|
2286
|
+
: 'if(ctx._cstBuf!==undefined||ctx._cstChildren!==undefined)pushCstChild(ctx,nd,rawEntry(nd,input,pos,end))'}
|
|
1808
2287
|
EC.e=end
|
|
1809
2288
|
return nd
|
|
1810
2289
|
}`
|
|
@@ -1834,7 +2313,9 @@ return nd
|
|
|
1834
2313
|
&& (flags === 2 || flags === 18 || flags === 34)) {
|
|
1835
2314
|
const fields = flags === 18
|
|
1836
2315
|
const collapseChildren = flags === 34
|
|
1837
|
-
const publish =
|
|
2316
|
+
const publish = L.buf && L.raw === RAW_OMIT
|
|
2317
|
+
? '_pushNodeNoRawBuf(ctx,nd)'
|
|
2318
|
+
: L.buf && L.raw === RAW_CAPTURE ? `if(sBuf!==undefined){
|
|
1838
2319
|
if(sBuf.rawOnly!==true){
|
|
1839
2320
|
if(sBuf.ch!==undefined)sBuf.ch.push(nd)
|
|
1840
2321
|
else if(sBuf.single!==undefined){sBuf.ch=[sBuf.single,nd];sBuf.single=undefined}
|
|
@@ -1847,6 +2328,22 @@ else sBuf.rawSingle=rawNd
|
|
|
1847
2328
|
}else if(sCh!==undefined){
|
|
1848
2329
|
sCh.push(nd)
|
|
1849
2330
|
if(sRaw!==undefined)sRaw.push(rawEntry(nd,input,pos,end))
|
|
2331
|
+
}` : `if(sBuf!==undefined){
|
|
2332
|
+
if(sBuf.noRaw===true)_pushNodeNoRawBuf(ctx,nd)
|
|
2333
|
+
else{
|
|
2334
|
+
if(sBuf.rawOnly!==true){
|
|
2335
|
+
if(sBuf.ch!==undefined)sBuf.ch.push(nd)
|
|
2336
|
+
else if(sBuf.single!==undefined){sBuf.ch=[sBuf.single,nd];sBuf.single=undefined}
|
|
2337
|
+
else sBuf.single=nd
|
|
2338
|
+
}
|
|
2339
|
+
const rawNd=rawEntry(nd,input,pos,end)
|
|
2340
|
+
if(sBuf.raw!==undefined)sBuf.raw.push(rawNd)
|
|
2341
|
+
else if(sBuf.rawSingle!==undefined){sBuf.raw=[sBuf.rawSingle,rawNd];sBuf.rawSingle=undefined}
|
|
2342
|
+
else sBuf.rawSingle=rawNd
|
|
2343
|
+
}
|
|
2344
|
+
}else if(sCh!==undefined){
|
|
2345
|
+
sCh.push(nd)
|
|
2346
|
+
if(sRaw!==undefined)sRaw.push(rawEntry(nd,input,pos,end))
|
|
1850
2347
|
}`
|
|
1851
2348
|
return `${head}
|
|
1852
2349
|
const sCh=ctx._cstChildren,sLv=ctx._cstLeaves,sRaw=ctx._cstRawChildren,sTl=ctx._cstTriviaLog
|
|
@@ -1888,6 +2385,7 @@ return nd
|
|
|
1888
2385
|
? !structural || grammarCapture || hostCapturesThisType !== false
|
|
1889
2386
|
: hostCapturesThisType === true
|
|
1890
2387
|
const keepChildren = !structural || cfg.hostReadsChildren !== false || collapse || unwrap
|
|
2388
|
+
const omitsRaw = !hostCst && build !== undefined && proj < 0 && (flags & 2) !== 0
|
|
1891
2389
|
const ty = q(type)
|
|
1892
2390
|
const stArg = readsState ? 'st' : '(ctx.state!==undefined?Object.assign({},ctx.state):undefined)'
|
|
1893
2391
|
const hostCall = `_pfHost(${ty},hostKids,fieldMap,span,rawKids,tlog,${stArg},${tags})`
|
|
@@ -1906,7 +2404,14 @@ return nd
|
|
|
1906
2404
|
// HOST COLLAPSE applies wherever the node's VALUE comes from the host —
|
|
1907
2405
|
// any node under a CST host, not only the builder-less ones.
|
|
1908
2406
|
const collapsible = keepChildren && (hostCst || (build === undefined && proj < 0))
|
|
1909
|
-
const openCapture =
|
|
2407
|
+
const openCapture = omitsRaw
|
|
2408
|
+
? `const buf={noRaw:true,rawLen:0}
|
|
2409
|
+
ctx._cstBuf=buf
|
|
2410
|
+
ctx._cstChildren=undefined
|
|
2411
|
+
ctx._cstLeaves=undefined
|
|
2412
|
+
ctx._cstRawChildren=undefined
|
|
2413
|
+
ctx._cstTriviaLog=undefined`
|
|
2414
|
+
: keepChildren
|
|
1910
2415
|
? `const buf={}
|
|
1911
2416
|
ctx._cstBuf=buf
|
|
1912
2417
|
ctx._cstChildren=undefined
|
|
@@ -1919,7 +2424,12 @@ ctx._cstChildren=undefined
|
|
|
1919
2424
|
ctx._cstLeaves=undefined
|
|
1920
2425
|
ctx._cstRawChildren=undefined
|
|
1921
2426
|
ctx._cstTriviaLog=undefined`
|
|
1922
|
-
const finishCapture =
|
|
2427
|
+
const finishCapture = omitsRaw
|
|
2428
|
+
? `const kids=buf.ch??(buf.single!==undefined?[buf.single]:EMPTY_CH)
|
|
2429
|
+
const hostKids=kids
|
|
2430
|
+
const rawKids=EMPTY_CH
|
|
2431
|
+
const tlog=buf.tl??EMPTY_TLOG`
|
|
2432
|
+
: keepChildren
|
|
1923
2433
|
? `const kids=buf.ch??(buf.single!==undefined?[buf.single]:EMPTY_CH)
|
|
1924
2434
|
const hostKids=kids
|
|
1925
2435
|
const rawKids=buf.raw??(buf.rawSingle!==undefined?[buf.rawSingle]:EMPTY_CH)
|
|
@@ -1956,11 +2466,15 @@ const st=${readsState ? '(ctx.state!==undefined?Object.assign({},ctx.state):unde
|
|
|
1956
2466
|
let nd
|
|
1957
2467
|
${unwrap ? 'if(kids.length===1)nd=unwrapChild(kids[0])\nelse ' : ''}${collapse ? 'if(kids.length===1)nd=kids[0]\nelse ' : ''}${collapsible ? `if(_pfHost!==undefined&&_pfHost._parsemanCstCollapse!==undefined&&kids.length===1&&rawKids.length===1&&_pfHost._parsemanCstCollapse(${ty},kids[0],kids,rawKids))nd=kids[0]
|
|
1958
2468
|
else ` : ''}{${value}}
|
|
1959
|
-
${L.buf
|
|
2469
|
+
${L.buf && L.raw === RAW_OMIT
|
|
2470
|
+
? '_pushNodeNoRawBuf(ctx,nd)'
|
|
2471
|
+
: L.buf && L.raw === RAW_CAPTURE
|
|
1960
2472
|
// The OUTER buffer, which this body saved into `sBuf` before opening its own —
|
|
1961
2473
|
// so an in-node site's parent collector is present by the same fact.
|
|
1962
2474
|
? 'pushCstChild(ctx,nd,rawEntry(nd,input,pos,end))'
|
|
1963
|
-
:
|
|
2475
|
+
: staticBuild
|
|
2476
|
+
? 'if(sBuf!==undefined||sCh!==undefined)pushCstChild(ctx,nd,sBuf!==undefined?(sBuf.noRaw===true?undefined:rawEntry(nd,input,pos,end)):sRaw!==undefined?rawEntry(nd,input,pos,end):undefined)'
|
|
2477
|
+
: 'if(sBuf!==undefined||sCh!==undefined)pushCstChild(ctx,nd,rawEntry(nd,input,pos,end))'}
|
|
1964
2478
|
EC.e=end
|
|
1965
2479
|
return nd
|
|
1966
2480
|
}`
|
|
@@ -1989,7 +2503,7 @@ return nd
|
|
|
1989
2503
|
// are `function` declarations, so a body may call one that is textually below
|
|
1990
2504
|
// it, but the `const _ts<N>` each one reads must be initialised before any
|
|
1991
2505
|
// parse runs, not merely before the declaration is evaluated.
|
|
1992
|
-
const source = `${RUNTIME_PRELUDE}
|
|
2506
|
+
const source = `${RUNTIME_PRELUDE}${needsNoRawPrelude ? NO_RAW_RUNTIME_PRELUDE : ''}
|
|
1993
2507
|
${prelude.join('\n')}
|
|
1994
2508
|
${skipDefs.join('\n')}
|
|
1995
2509
|
${choiceDefs.join('\n')}
|
|
@@ -2004,7 +2518,13 @@ _pfHost=host
|
|
|
2004
2518
|
function _finish(){
|
|
2005
2519
|
if(_pfDepth<=0)throw new Error('parseman emitted table assembly frame underflow')
|
|
2006
2520
|
_pfDepth--
|
|
2007
|
-
if(_pfDepth===0)
|
|
2521
|
+
if(_pfDepth===0){
|
|
2522
|
+
_pfTokInput=undefined
|
|
2523
|
+
_pfTokBody=-1
|
|
2524
|
+
_pfTokValue=undefined
|
|
2525
|
+
_pfTokDispatch=-1
|
|
2526
|
+
return
|
|
2527
|
+
}
|
|
2008
2528
|
const prior=_pfFrames.pop()
|
|
2009
2529
|
_pfScan=prior[0]
|
|
2010
2530
|
_pfHost=prior[1]
|