parseman 0.51.1 → 0.52.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.map +1 -1
- package/dist/analysis/diagnostics.js +101 -35
- package/dist/analysis/diagnostics.js.map +1 -1
- package/dist/cli/index.js +103 -37
- package/dist/cli/index.js.map +1 -1
- package/dist/combinators/attempt.d.ts +11 -1
- package/dist/combinators/attempt.d.ts.map +1 -1
- package/dist/combinators/map.d.ts.map +1 -1
- package/dist/combinators/node.d.ts +6 -2
- package/dist/combinators/node.d.ts.map +1 -1
- package/dist/combinators/token.d.ts +8 -8
- package/dist/combinators/token.d.ts.map +1 -1
- package/dist/compiler/token-capability.js +4 -0
- package/dist/compiler/token-capability.js.map +1 -1
- package/dist/coverage.d.ts.map +1 -1
- package/dist/cst/trivia-kinds.d.ts.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +107 -40
- package/dist/index.js.map +1 -1
- package/dist/plugin/evaluator.d.ts.map +1 -1
- package/dist/plugin/index.js +107 -41
- package/dist/plugin/index.js.map +1 -1
- package/dist/spec/index.js +55 -10
- package/dist/spec/index.js.map +1 -1
- package/dist/table/assemble.d.ts.map +1 -1
- package/dist/table/emit-assembly.d.ts.map +1 -1
- package/dist/table/encode-baseline.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 +404 -371
- package/dist/table/index.js.map +2 -2
- package/dist/table/ops.d.ts +9 -5
- package/dist/table/ops.d.ts.map +1 -1
- package/dist/table/site-labels.d.ts.map +1 -1
- package/dist/types.d.ts +9 -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 +3 -1
- package/src/analysis/duplication.ts +1 -1
- package/src/analysis/rebuild.ts +3 -3
- package/src/combinators/attempt.ts +26 -3
- package/src/combinators/first-set.ts +5 -1
- package/src/combinators/map.ts +5 -1
- package/src/combinators/node.ts +12 -3
- package/src/combinators/token.ts +46 -3
- package/src/compiler/ir-serialize.ts +5 -5
- package/src/coverage.ts +3 -4
- package/src/cst/trivia-kinds.ts +6 -0
- package/src/index.ts +2 -1
- package/src/plugin/evaluator.ts +8 -5
- package/src/table/assemble.ts +30 -11
- package/src/table/child-slots.ts +2 -2
- package/src/table/emit-assembly.ts +13 -6
- package/src/table/encode-baseline.ts +8 -3
- package/src/table/encode.ts +16 -16
- package/src/table/exec-baseline.ts +30 -4
- package/src/table/exec.ts +14 -5
- package/src/table/ops.ts +10 -6
- package/src/table/scalar-terminal.ts +5 -5
- package/src/table/site-labels.ts +5 -3
- package/src/types.ts +9 -2
- package/src/version.ts +1 -1
package/src/table/assemble.ts
CHANGED
|
@@ -80,7 +80,7 @@ import {
|
|
|
80
80
|
type CstCaptureBuf,
|
|
81
81
|
} from '../cst/capture-buffer.ts'
|
|
82
82
|
import {
|
|
83
|
-
OP_CHOICE, OP_EMPTY, OP_GATE, OP_LEAF, OP_LIT, OP_NODE, OP_NOT, OP_OPT,
|
|
83
|
+
OP_CHOICE, OP_EMPTY, OP_GATE, OP_LEAF, OP_SOURCE_LEAF, OP_LIT, OP_NODE, OP_NOT, OP_OPT,
|
|
84
84
|
OP_PEEK, OP_REP, OP_REPV, OP_RULE, OP_RX, OP_SEQ, OP_SEQV, OP_XFORM,
|
|
85
85
|
OP_LIT_TRACK, OP_RX_TRACK, OP_NODE_TRACK, OP_SCOPE, OP_SCOPE_CAP, OP_SCOPE_PLAIN, OP_EXPECT, OP_SEQX, OP_SCAN,
|
|
86
86
|
OP_LIVE,
|
|
@@ -523,8 +523,13 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
523
523
|
* exact AST workloads execute the three shapes below 42k/66k/205k times per
|
|
524
524
|
* CSS/Less/generated parse. Keep their bodies scalar and keep every other
|
|
525
525
|
* shape on the generic oracle below.
|
|
526
|
+
*
|
|
527
|
+
* `trailing` is the node's `trailingTrivia` bit: consume the active trivia
|
|
528
|
+
* after a successful body, inside the node's span, exactly where the generic
|
|
529
|
+
* body does. It opens no capture: these shapes are the reducers that read no
|
|
530
|
+
* trivia log.
|
|
526
531
|
*/
|
|
527
|
-
function plainBuildNode(child: Piece, build: NodeBuilder): Piece {
|
|
532
|
+
function plainBuildNode(child: Piece, build: NodeBuilder, trailing: boolean): Piece {
|
|
528
533
|
return (input, pos, ctx) => {
|
|
529
534
|
const sCh = ctx._cstChildren
|
|
530
535
|
const sLv = ctx._cstLeaves
|
|
@@ -542,6 +547,7 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
542
547
|
ctx.captureTrivia = false
|
|
543
548
|
ctx._fields = undefined
|
|
544
549
|
const value = child(input, pos, ctx)
|
|
550
|
+
if (trailing && value !== FAIL && ctx.trivia !== undefined) EC.e = consumeTrivia(input, EC.e, ctx)
|
|
545
551
|
const kids = buf.ch ?? (buf.single !== undefined ? [buf.single] : EMPTY_CH)
|
|
546
552
|
const rawKids = buf.raw ?? (buf.rawSingle !== undefined ? [buf.rawSingle] : EMPTY_CH)
|
|
547
553
|
ctx._fields = savedFields
|
|
@@ -578,7 +584,7 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
578
584
|
/** Direct reducer whose declared arity proves `rawChildren` is unobservable.
|
|
579
585
|
* Use the existing split children/leaves collector instead of opening a
|
|
580
586
|
* duplicate raw capture buffer. The body shape is selected while linking. */
|
|
581
|
-
function childrenOnlyBuildNode(child: Piece, build: NodeBuilder): Piece {
|
|
587
|
+
function childrenOnlyBuildNode(child: Piece, build: NodeBuilder, trailing: boolean): Piece {
|
|
582
588
|
return (input, pos, ctx) => {
|
|
583
589
|
const sCh = ctx._cstChildren
|
|
584
590
|
const sLv = ctx._cstLeaves
|
|
@@ -596,6 +602,7 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
596
602
|
ctx.captureTrivia = false
|
|
597
603
|
ctx._fields = undefined
|
|
598
604
|
const value = child(input, pos, ctx)
|
|
605
|
+
if (trailing && value !== FAIL && ctx.trivia !== undefined) EC.e = consumeTrivia(input, EC.e, ctx)
|
|
599
606
|
const captured = capturedFlatChildren(kids)
|
|
600
607
|
ctx._fields = savedFields
|
|
601
608
|
ctx._cstBuf = sBuf
|
|
@@ -1912,6 +1919,7 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
1912
1919
|
|
|
1913
1920
|
case OP_ATTEMPT: {
|
|
1914
1921
|
const child = link(code[ip + 1]!)
|
|
1922
|
+
const contain = code[ip + 2] === 1
|
|
1915
1923
|
return (input, pos, ctx) => {
|
|
1916
1924
|
const need = markCst(ctx)
|
|
1917
1925
|
const mRaw = MRAW
|
|
@@ -1924,8 +1932,12 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
1924
1932
|
const v = child(input, pos, ctx)
|
|
1925
1933
|
if (v !== FAIL) return v
|
|
1926
1934
|
if (need) rollbackTriviaAt(ctx, mRaw, mTl, mLv, mFl, mEr, mLog, mRoot)
|
|
1927
|
-
// A committed failure propagates VERBATIM — rolled back, not re-anchored
|
|
1928
|
-
|
|
1935
|
+
// A committed failure propagates VERBATIM — rolled back, not re-anchored —
|
|
1936
|
+
// unless the row contains commitment, where it is reported like any other.
|
|
1937
|
+
if (committed(ctx)) {
|
|
1938
|
+
if (!contain) return FAIL
|
|
1939
|
+
ctx._fc = false
|
|
1940
|
+
}
|
|
1929
1941
|
ctx._fe = pos
|
|
1930
1942
|
return FAIL
|
|
1931
1943
|
}
|
|
@@ -2149,7 +2161,8 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
2149
2161
|
}
|
|
2150
2162
|
}
|
|
2151
2163
|
|
|
2152
|
-
case OP_LEAF:
|
|
2164
|
+
case OP_LEAF:
|
|
2165
|
+
case OP_SOURCE_LEAF: {
|
|
2153
2166
|
const fn = fns[code[ip + 1]!] as (value: unknown, span: { start: number; end: number }) => unknown
|
|
2154
2167
|
const child = link(code[ip + 2]!)
|
|
2155
2168
|
return (input, pos, ctx) => {
|
|
@@ -2179,7 +2192,11 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
2179
2192
|
if (v === FAIL) return FAIL
|
|
2180
2193
|
const end = EC.e
|
|
2181
2194
|
const out = fn(v, { start: pos, end })
|
|
2182
|
-
if (wasCapturing) pushCstLeaf(ctx, {
|
|
2195
|
+
if (wasCapturing) pushCstLeaf(ctx, {
|
|
2196
|
+
_tag: 'leaf',
|
|
2197
|
+
value: op === OP_SOURCE_LEAF && hostCst ? input.slice(pos, end) : out,
|
|
2198
|
+
span: { start: pos, end },
|
|
2199
|
+
})
|
|
2183
2200
|
EC.e = end
|
|
2184
2201
|
return out
|
|
2185
2202
|
}
|
|
@@ -3457,11 +3474,13 @@ export function assemble(t: ResolvedTable, prog: TableProgram, cfg: RunCfg): Ass
|
|
|
3457
3474
|
// data plus this assembly's fixed option set; direct/custom contexts and
|
|
3458
3475
|
// every richer node shape retain the generic implementation below.
|
|
3459
3476
|
if (!hostCst && !tracked) {
|
|
3460
|
-
|
|
3461
|
-
|
|
3477
|
+
// The trailing-trivia bit (128) rides on the two leanest shapes; it adds
|
|
3478
|
+
// a consume, not a capture.
|
|
3479
|
+
if (build !== undefined && proj < 0 && (flags & ~128) === 0) {
|
|
3480
|
+
return plainBuildNode(child, build, trailingTrivia)
|
|
3462
3481
|
}
|
|
3463
|
-
if (build !== undefined && proj < 0 && flags === 2) {
|
|
3464
|
-
return childrenOnlyBuildNode(child, build)
|
|
3482
|
+
if (build !== undefined && proj < 0 && (flags & ~128) === 2) {
|
|
3483
|
+
return childrenOnlyBuildNode(child, build, trailingTrivia)
|
|
3465
3484
|
}
|
|
3466
3485
|
if (build !== undefined && proj < 0 && flags === 18) {
|
|
3467
3486
|
return childrenOnlyFieldsBuildNode(child, build)
|
package/src/table/child-slots.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
OP_ADJ, OP_ARMGATE, OP_ATTEMPT, OP_CHOICE, OP_COV, OP_DISPATCH, OP_EMPTY, OP_EXPECT,
|
|
3
|
-
OP_FIELD, OP_GATE, OP_GREEDY, OP_GUARD, OP_LABEL, OP_LEAF, OP_LIT, OP_LIT_CI,
|
|
3
|
+
OP_FIELD, OP_GATE, OP_GREEDY, OP_GUARD, OP_LABEL, OP_LEAF, OP_SOURCE_LEAF, OP_LIT, OP_LIT_CI,
|
|
4
4
|
OP_LIT_CI_TRACK, OP_LIT_TRACK, OP_LIVE, OP_NODE, OP_NODE_TRACK, OP_NOT, OP_OPT,
|
|
5
5
|
OP_PEEK, OP_REJECT, OP_REP, OP_REPV, OP_ROUTED, OP_RULE, OP_RX, OP_RX_TRACK, OP_SCAN,
|
|
6
6
|
OP_SCOPE, OP_SCOPE_CAP, OP_SCOPE_PLAIN, OP_SEQ, OP_SEQV, OP_SEQX, OP_TOKEN, OP_WITHCTX, OP_XFORM,
|
|
@@ -55,7 +55,7 @@ export function childSlots(code: ArrayLike<number>, ip: number, out: number[]):
|
|
|
55
55
|
|
|
56
56
|
// Single child at `ip + 2`.
|
|
57
57
|
case OP_GATE: case OP_SCOPE: case OP_SCOPE_CAP: case OP_SCOPE_PLAIN: case OP_WITHCTX: case OP_XFORM:
|
|
58
|
-
case OP_NODE: case OP_NODE_TRACK: case OP_FIELD: case OP_LEAF: case OP_ARMGATE:
|
|
58
|
+
case OP_NODE: case OP_NODE_TRACK: case OP_FIELD: case OP_LEAF: case OP_SOURCE_LEAF: case OP_ARMGATE:
|
|
59
59
|
out.push(code[ip + 2]!)
|
|
60
60
|
return true
|
|
61
61
|
|
|
@@ -48,7 +48,7 @@ import type { ParseContext } from '../types.ts'
|
|
|
48
48
|
import { regexCanMatchEmpty } from '../regex/first-set.ts'
|
|
49
49
|
import {
|
|
50
50
|
OP_ADJ, OP_ATTEMPT, OP_CHOICE, OP_DISPATCH, OP_EMPTY, OP_EXPECT, OP_FIELD, OP_GATE,
|
|
51
|
-
OP_LABEL, OP_LEAF, OP_LIT, OP_LIT_CI, OP_LIT_CI_TRACK, OP_LIT_TRACK, OP_NAMES,
|
|
51
|
+
OP_LABEL, OP_LEAF, OP_SOURCE_LEAF, OP_LIT, OP_LIT_CI, OP_LIT_CI_TRACK, OP_LIT_TRACK, OP_NAMES,
|
|
52
52
|
OP_NODE, OP_NODE_TRACK, OP_NOT, OP_OPT, OP_PEEK, OP_REP, OP_REPV, OP_ROUTED, OP_RULE, OP_RX,
|
|
53
53
|
OP_RX_TRACK, OP_SCAN, OP_SCOPE, OP_SCOPE_CAP, OP_SCOPE_PLAIN, OP_SEQ, OP_SEQV, OP_SEQX, OP_TOKEN, OP_XFORM,
|
|
54
54
|
OP_LEX_BODY, OP_LEX_PROGRAM,
|
|
@@ -1411,8 +1411,10 @@ ${rootCap ? 'ctx._rootTriviaCapture=sR\n' : ''}return v
|
|
|
1411
1411
|
/* ── boundaries ──────────────────────────────────────────────────────── */
|
|
1412
1412
|
|
|
1413
1413
|
case OP_TOKEN:
|
|
1414
|
-
case OP_LEAF:
|
|
1414
|
+
case OP_LEAF:
|
|
1415
|
+
case OP_SOURCE_LEAF: {
|
|
1415
1416
|
const isToken = op === OP_TOKEN
|
|
1417
|
+
const capturesSource = isToken || (op === OP_SOURCE_LEAF && hostCst)
|
|
1416
1418
|
const fn = isToken ? undefined : fnRef(code[ip + 1]!)
|
|
1417
1419
|
const child = link(isToken ? code[ip + 1]! : code[ip + 2]!)
|
|
1418
1420
|
// `try`/`finally`, AS `assemble.ts` HAS IT. `OP_SCOPE` restores linearly
|
|
@@ -1460,7 +1462,7 @@ ctx._triviaLog=sOtl
|
|
|
1460
1462
|
if(v===FAIL)return FAIL
|
|
1461
1463
|
const e=EC.e
|
|
1462
1464
|
const out=${isToken ? 'input.slice(pos,e)' : `${fn}(v,{start:pos,end:e})`}
|
|
1463
|
-
if(wasCap)pushCstLeaf(ctx,{_tag:'leaf',value:out,span:{start:pos,end:e}})
|
|
1465
|
+
if(wasCap)pushCstLeaf(ctx,{_tag:'leaf',value:${capturesSource ? 'input.slice(pos,e)' : 'out'},span:{start:pos,end:e}})
|
|
1464
1466
|
EC.e=e
|
|
1465
1467
|
return out
|
|
1466
1468
|
}`
|
|
@@ -1550,7 +1552,7 @@ ${clean ? '' : emitMark(p, L.buf, L.raw, sinks)}
|
|
|
1550
1552
|
const v=${child}(input,pos,ctx)
|
|
1551
1553
|
if(v!==FAIL)return v
|
|
1552
1554
|
${clean ? '' : emitRollback(p, L.buf, L.raw, sinks)}
|
|
1553
|
-
if(ctx._fc===true)return FAIL
|
|
1555
|
+
${code[ip + 2] === 1 ? 'ctx._fc=false' : 'if(ctx._fc===true)return FAIL'}
|
|
1554
1556
|
ctx._fe=pos
|
|
1555
1557
|
return FAIL
|
|
1556
1558
|
}`
|
|
@@ -2332,8 +2334,11 @@ return nd
|
|
|
2332
2334
|
// not closure-only semantics. Print the same body for a precompiled
|
|
2333
2335
|
// assembly so a large composeLeaf artifact does not fall back to opening
|
|
2334
2336
|
// the generic raw/trivia buffer that its reducer arity proved unread.
|
|
2337
|
+
// The trailing-trivia bit (128) rides on the children-only shape as it
|
|
2338
|
+
// does in the closure assembler: a consume after the body, no capture.
|
|
2339
|
+
const leanFlags = flags & ~128
|
|
2335
2340
|
if (!hostCst && !tracked && build !== undefined && proj < 0
|
|
2336
|
-
&& (
|
|
2341
|
+
&& (leanFlags === 2 || (flags === 18 || flags === 34))) {
|
|
2337
2342
|
const fields = flags === 18
|
|
2338
2343
|
const collapseChildren = flags === 34
|
|
2339
2344
|
const publish = L.buf && L.raw === RAW_OMIT
|
|
@@ -2380,7 +2385,9 @@ ctx._cstTriviaLog=undefined
|
|
|
2380
2385
|
ctx.captureTrivia=false
|
|
2381
2386
|
ctx._fields=${fields ? '[]' : 'undefined'}
|
|
2382
2387
|
const v=${child}(input,pos,ctx)
|
|
2383
|
-
|
|
2388
|
+
${trailingTrivia && L.tri !== TRI_NONE
|
|
2389
|
+
? `if(v!==FAIL${L.tri === TRI_UNKNOWN ? '&&ctx.trivia!==undefined' : ''})EC.e=consumeTrivia(input,EC.e,ctx)\n`
|
|
2390
|
+
: ''}const captured=_capturedFlatChildren(flat)
|
|
2384
2391
|
${fields ? 'const fieldMap=buildFieldMap(ctx._fields)\n' : ''}ctx._fields=sFields
|
|
2385
2392
|
ctx._cstBuf=sBuf
|
|
2386
2393
|
ctx._cstChildren=sCh
|
|
@@ -3,7 +3,7 @@ import { firstSetOf, matchesEmpty } from '../combinators/first-set.ts'
|
|
|
3
3
|
import { deriveExpected } from '../combinators/expect.ts'
|
|
4
4
|
import { buildReadsState, buildReadsTrivia } from '../compiler/build-arity.ts'
|
|
5
5
|
import {
|
|
6
|
-
OP_CHOICE, OP_EMPTY, OP_GATE, OP_LEAF, OP_LIT, OP_NODE, OP_NOT, OP_OPT,
|
|
6
|
+
OP_CHOICE, OP_EMPTY, OP_GATE, OP_LEAF, OP_SOURCE_LEAF, OP_LIT, OP_NODE, OP_NOT, OP_OPT,
|
|
7
7
|
OP_PEEK, OP_REP, OP_REPV, OP_RULE, OP_RX, OP_SEQ, OP_SEQV, OP_XFORM,
|
|
8
8
|
OP_LIT_TRACK, OP_RX_TRACK, OP_NODE_TRACK, OP_SCOPE, OP_EXPECT,
|
|
9
9
|
} from './ops.ts'
|
|
@@ -265,7 +265,7 @@ class Encoder {
|
|
|
265
265
|
}
|
|
266
266
|
case 'leaf': {
|
|
267
267
|
const child = this.node(d.parser).ip
|
|
268
|
-
return this.emit(OP_LEAF, this.fn(d.fn), child)
|
|
268
|
+
return this.emit(d.cstValue === 'source' ? OP_SOURCE_LEAF : OP_LEAF, this.fn(d.fn), child)
|
|
269
269
|
}
|
|
270
270
|
case 'node': {
|
|
271
271
|
if (d.unwrap || d.collapse || d.project !== undefined) throw new UnsupportedConstructBaseline('node(unwrap|collapse|project)')
|
|
@@ -295,9 +295,13 @@ class Encoder {
|
|
|
295
295
|
return this.emit(OP_NOT, this.node(d.parser).ip)
|
|
296
296
|
case 'peek':
|
|
297
297
|
return this.emit(OP_PEEK, this.node(d.parser).ip)
|
|
298
|
+
// A contained transaction changes what an enclosing choice does next, so
|
|
299
|
+
// it cannot be transparent.
|
|
300
|
+
case 'attempt':
|
|
301
|
+
if (d.contain === true) throw new UnsupportedConstructBaseline('attempt(contain)')
|
|
302
|
+
return this.node(d.parser).ip
|
|
298
303
|
// Transparent wrappers: no row of their own, no dispatch at run time.
|
|
299
304
|
case 'token':
|
|
300
|
-
case 'attempt':
|
|
301
305
|
case 'label':
|
|
302
306
|
case 'trivia':
|
|
303
307
|
return this.node(d.parser).ip
|
|
@@ -319,6 +323,7 @@ class Encoder {
|
|
|
319
323
|
code: this.code, k: this.k, fns: this.fns, cc: this.cc,
|
|
320
324
|
fx: this.fx, disp: this.disp, dsp: [], rules: this.rules,
|
|
321
325
|
lines: this.track ? 1 : 0,
|
|
326
|
+
...(this.settings.hostMode === undefined ? {} : { hostMode: this.settings.hostMode }),
|
|
322
327
|
}
|
|
323
328
|
}
|
|
324
329
|
}
|
package/src/table/encode.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { buildReadsRaw, buildReadsState, buildReadsTrivia } from '../compiler/bu
|
|
|
9
9
|
import { buildReadsFields, parserHasOwnFields } from '../compiler/fields.ts'
|
|
10
10
|
import { asciiFoldKey, branchUsesRouted, parserUsesRouted } from '../combinators/dispatch.ts'
|
|
11
11
|
import {
|
|
12
|
-
OP_CHOICE, OP_EMPTY, OP_GATE, OP_LEAF, OP_LIT, OP_NODE, OP_NOT, OP_OPT,
|
|
12
|
+
OP_CHOICE, OP_EMPTY, OP_GATE, OP_LEAF, OP_SOURCE_LEAF, OP_LIT, OP_NODE, OP_NOT, OP_OPT,
|
|
13
13
|
OP_PEEK, OP_REP, OP_REPV, OP_RULE, OP_RX, OP_SEQ, OP_SEQV, OP_XFORM,
|
|
14
14
|
OP_LIT_TRACK, OP_RX_TRACK, OP_NODE_TRACK, OP_SCOPE, OP_SCOPE_CAP, OP_SCOPE_PLAIN, OP_EXPECT, OP_SEQX, OP_SCAN,
|
|
15
15
|
OP_LIVE, OP_ATTEMPT, OP_LABEL,
|
|
@@ -1322,7 +1322,7 @@ class Encoder {
|
|
|
1322
1322
|
}
|
|
1323
1323
|
case 'leaf': {
|
|
1324
1324
|
const child = this.node(d.parser).ip
|
|
1325
|
-
return this.emit(OP_LEAF, this.fn(d.fn, d.fnSrc ?? null), child)
|
|
1325
|
+
return this.emit(d.cstValue === 'source' ? OP_SOURCE_LEAF : OP_LEAF, this.fn(d.fn, d.fnSrc ?? null), child)
|
|
1326
1326
|
}
|
|
1327
1327
|
case 'node': {
|
|
1328
1328
|
// A node legally has NO builder when its value comes from a selection:
|
|
@@ -1344,24 +1344,24 @@ class Encoder {
|
|
|
1344
1344
|
// 'cst'` forces them on, exactly as the emitted `cstOut` path does. The
|
|
1345
1345
|
// driver reads a bit; it re-derives nothing and sees no setting.
|
|
1346
1346
|
const cstOut = this.settings.hostMode === 'cst'
|
|
1347
|
-
// THE THREE DERIVED CAPTURE BITS, mirroring `node.ts
|
|
1347
|
+
// THE THREE DERIVED CAPTURE BITS, mirroring `node.ts` term for term:
|
|
1348
1348
|
//
|
|
1349
|
-
// capturesTrivia = captureTrivia
|
|
1349
|
+
// capturesTrivia = captureTrivia
|
|
1350
1350
|
// || (build ? buildReadsTrivia(def) : project === undefined)
|
|
1351
1351
|
//
|
|
1352
1352
|
// `cstOut` is the static stand-in for "a CST host is coming", which the
|
|
1353
1353
|
// interpreter reaches dynamically off `ctx.build`.
|
|
1354
1354
|
//
|
|
1355
|
-
//
|
|
1355
|
+
// `captureTrivia` was once MISSING here and was a real divergence: an
|
|
1356
|
+
// explicit request the arity analysis cannot express (an author can ask
|
|
1357
|
+
// for capture on a 3-argument reducer). This used to REFUSE rather than
|
|
1358
|
+
// lower, so a documented option no grammar could use through the table.
|
|
1356
1359
|
//
|
|
1357
|
-
//
|
|
1358
|
-
//
|
|
1359
|
-
//
|
|
1360
|
-
//
|
|
1361
|
-
//
|
|
1362
|
-
// lands in THIS node's log. The interpreter counts it; the table did
|
|
1363
|
-
// not, so a node with `trailingTrivia` and a non-trivia-reading
|
|
1364
|
-
// reducer captured under the interpreter and dropped under the table.
|
|
1360
|
+
// `trailingTrivia` is NOT a capture term (bit 128 only). It moves the
|
|
1361
|
+
// node's end past the trivia after the body; that trivia reaches the root
|
|
1362
|
+
// table with or without a node log, and only a log the reducer reads is
|
|
1363
|
+
// worth opening. Forcing bit 4 for it put every match of a
|
|
1364
|
+
// non-trivia-reading node on the generic capture path.
|
|
1365
1365
|
//
|
|
1366
1366
|
// The `build ? … : project === undefined` split matters now that a
|
|
1367
1367
|
// STRUCTURAL node lowers: with no builder the interpreter captures unless
|
|
@@ -1374,7 +1374,7 @@ class Encoder {
|
|
|
1374
1374
|
// Field capture additionally requires the body to CONTAIN `field()`
|
|
1375
1375
|
// captures: a node that reads fields but has none allocates nothing.
|
|
1376
1376
|
const wantsFields = parserHasOwnFields(d.parser) && (cstOut || derivedFields)
|
|
1377
|
-
const flags = (cstOut || d.captureTrivia === true ||
|
|
1377
|
+
const flags = (cstOut || d.captureTrivia === true || derivedTrivia ? 4 : 0)
|
|
1378
1378
|
| (!cstOut && omitsRaw ? 2 : 0)
|
|
1379
1379
|
| (cstOut || derivedState ? 8 : 0)
|
|
1380
1380
|
| (wantsFields ? 16 : 0)
|
|
@@ -1596,7 +1596,7 @@ class Encoder {
|
|
|
1596
1596
|
// A TRANSACTION IS A ROW. See `OP_ATTEMPT` for why the transparent
|
|
1597
1597
|
// lowering was correct only for a choice arm.
|
|
1598
1598
|
case 'attempt': {
|
|
1599
|
-
const inner = this.emit(OP_ATTEMPT, this.node(d.parser).ip)
|
|
1599
|
+
const inner = this.emit(OP_ATTEMPT, this.node(d.parser).ip, d.contain === true ? 1 : 0)
|
|
1600
1600
|
if (!this.failureNeedsRollback(d.parser, undefined)) this.failureRollbackCleanSites.add(inner)
|
|
1601
1601
|
// THE FIRST-SET FAIL-FAST GUARD, lowered as the `OP_GATE` row the `node()`
|
|
1602
1602
|
// case already uses — it is the same guard, written twice in the
|
|
@@ -1737,7 +1737,7 @@ class Encoder {
|
|
|
1737
1737
|
switch (this.code[ip]) {
|
|
1738
1738
|
case OP_GATE: return [ip + 2]
|
|
1739
1739
|
case OP_RULE: case OP_OPT: case OP_NOT: case OP_PEEK: case OP_EXPECT: case OP_ATTEMPT: case OP_LABEL: case OP_COV: return [ip + 1]
|
|
1740
|
-
case OP_SCOPE: case OP_SCOPE_CAP: case OP_SCOPE_PLAIN: case OP_WITHCTX: case OP_XFORM: case OP_LEAF: case OP_NODE: case OP_NODE_TRACK: return [ip + 2]
|
|
1740
|
+
case OP_SCOPE: case OP_SCOPE_CAP: case OP_SCOPE_PLAIN: case OP_WITHCTX: case OP_XFORM: case OP_LEAF: case OP_SOURCE_LEAF: case OP_NODE: case OP_NODE_TRACK: return [ip + 2]
|
|
1741
1741
|
case OP_SEQ: case OP_SEQV: return Array.from({ length: this.code[ip + 1]! }, (_, i) => ip + 2 + i)
|
|
1742
1742
|
case OP_SEQX: return Array.from({ length: this.code[ip + 2]! }, (_, i) => ip + 3 + i)
|
|
1743
1743
|
// ARMS START AT ip+4. `ip+3` is the choice's own EXPECTED-SET index, and
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
pushCstLeaf, rollbackCstCapture, saveCstMark, type CstRollbackMark,
|
|
6
6
|
} from '../cst/capture-buffer.ts'
|
|
7
7
|
import {
|
|
8
|
-
OP_CHOICE, OP_EMPTY, OP_GATE, OP_LEAF, OP_LIT, OP_NODE, OP_NOT, OP_OPT,
|
|
8
|
+
OP_CHOICE, OP_EMPTY, OP_GATE, OP_LEAF, OP_SOURCE_LEAF, OP_LIT, OP_NODE, OP_NOT, OP_OPT,
|
|
9
9
|
OP_PEEK, OP_REP, OP_REPV, OP_RULE, OP_RX, OP_SEQ, OP_SEQV, OP_XFORM,
|
|
10
10
|
OP_LIT_TRACK, OP_RX_TRACK, OP_NODE_TRACK, OP_SCOPE, OP_EXPECT,
|
|
11
11
|
} from './ops.ts'
|
|
@@ -94,6 +94,7 @@ function makeDriver(
|
|
|
94
94
|
cc: readonly ResolvedClass[],
|
|
95
95
|
fx: readonly (readonly string[])[],
|
|
96
96
|
disp: readonly ResolvedDispatch[],
|
|
97
|
+
hostCst: boolean,
|
|
97
98
|
): Driver {
|
|
98
99
|
/** Shared end-position out-parameter (`_pfEnd` in emitted code). */
|
|
99
100
|
let END = 0
|
|
@@ -434,12 +435,37 @@ function makeDriver(
|
|
|
434
435
|
return fn(v, { start: pos, end: END })
|
|
435
436
|
}
|
|
436
437
|
|
|
437
|
-
case OP_LEAF:
|
|
438
|
-
|
|
438
|
+
case OP_LEAF:
|
|
439
|
+
case OP_SOURCE_LEAF: {
|
|
440
|
+
const sBuf = ctx._cstBuf, sChildren = ctx._cstChildren, sLeaves = ctx._cstLeaves
|
|
441
|
+
const sRaw = ctx._cstRawChildren, sTl = ctx._cstTriviaLog
|
|
442
|
+
const sOuterTl = ctx._triviaLog
|
|
443
|
+
const wasCapturing = cstCaptureActive(ctx)
|
|
444
|
+
ctx._cstBuf = undefined
|
|
445
|
+
ctx._cstChildren = undefined
|
|
446
|
+
ctx._cstLeaves = undefined
|
|
447
|
+
ctx._cstRawChildren = undefined
|
|
448
|
+
ctx._cstTriviaLog = undefined
|
|
449
|
+
ctx._triviaLog = undefined
|
|
450
|
+
let v: unknown
|
|
451
|
+
try { v = exec(code[ip + 2]!, input, pos, ctx) }
|
|
452
|
+
finally {
|
|
453
|
+
ctx._cstBuf = sBuf
|
|
454
|
+
ctx._cstChildren = sChildren
|
|
455
|
+
ctx._cstLeaves = sLeaves
|
|
456
|
+
ctx._cstRawChildren = sRaw
|
|
457
|
+
ctx._cstTriviaLog = sTl
|
|
458
|
+
ctx._triviaLog = sOuterTl
|
|
459
|
+
}
|
|
439
460
|
if (v === FAIL) return FAIL
|
|
440
461
|
const end = END
|
|
441
462
|
const fn = fns[code[ip + 1]!] as (value: unknown, span: { start: number; end: number }) => unknown
|
|
442
463
|
const out = fn(v, { start: pos, end })
|
|
464
|
+
if (wasCapturing) pushCstLeaf(ctx, {
|
|
465
|
+
_tag: 'leaf',
|
|
466
|
+
value: code[ip] === OP_SOURCE_LEAF && hostCst ? input.slice(pos, end) : out,
|
|
467
|
+
span: { start: pos, end },
|
|
468
|
+
})
|
|
443
469
|
END = end
|
|
444
470
|
return out
|
|
445
471
|
}
|
|
@@ -509,7 +535,7 @@ function makeDriver(
|
|
|
509
535
|
export function execRulesBaseline(source: TableProgram | CompactProgram): Record<string, TableRule> {
|
|
510
536
|
const prog = expandCompact(source)
|
|
511
537
|
const t = resolveTable(prog)
|
|
512
|
-
const d = makeDriver(t.code, t.k, t.fns, t.cc, t.fx, t.disp)
|
|
538
|
+
const d = makeDriver(t.code, t.k, t.fns, t.cc, t.fx, t.disp, prog.hostMode === 'cst')
|
|
513
539
|
const out: Record<string, TableRule> = {}
|
|
514
540
|
// Chosen ONCE, from table data, at rule-map construction. Not a per-parse
|
|
515
541
|
// branch on an option: a plain table never has this wrapper at all.
|
package/src/table/exec.ts
CHANGED
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
demoteCapturedToRaw, endCstNodeCapture, pushCstChild, pushCstLeaf,
|
|
16
16
|
} from '../cst/capture-buffer.ts'
|
|
17
17
|
import {
|
|
18
|
-
OP_CHOICE, OP_EMPTY, OP_GATE, OP_LEAF, OP_LIT, OP_NODE, OP_NOT, OP_OPT,
|
|
18
|
+
OP_CHOICE, OP_EMPTY, OP_GATE, OP_LEAF, OP_SOURCE_LEAF, OP_LIT, OP_NODE, OP_NOT, OP_OPT,
|
|
19
19
|
OP_PEEK, OP_REP, OP_REPV, OP_RULE, OP_RX, OP_SEQ, OP_SEQV, OP_XFORM,
|
|
20
20
|
OP_LIT_TRACK, OP_RX_TRACK, OP_NODE_TRACK, OP_SCOPE, OP_SCOPE_CAP, OP_SCOPE_PLAIN, OP_EXPECT, OP_SEQX, OP_SCAN,
|
|
21
21
|
OP_LIVE,
|
|
@@ -1371,7 +1371,8 @@ function makeDriver(
|
|
|
1371
1371
|
return fn(v, { start: pos, end: EC.e })
|
|
1372
1372
|
}
|
|
1373
1373
|
|
|
1374
|
-
case OP_LEAF:
|
|
1374
|
+
case OP_LEAF:
|
|
1375
|
+
case OP_SOURCE_LEAF: {
|
|
1375
1376
|
// Mirrors src/combinators/token.ts:89-127. `leaf()` is a CAPTURE
|
|
1376
1377
|
// BOUNDARY: it suppresses the interior's own CST captures and exposes
|
|
1377
1378
|
// exactly ONE leaf carrying the reducer's value. Running the interior
|
|
@@ -1410,7 +1411,11 @@ function makeDriver(
|
|
|
1410
1411
|
const fn = fns[code[ip + 1]!] as (value: unknown, span: { start: number; end: number }) => unknown
|
|
1411
1412
|
if (COUNT) siteFn('LEAF fn()', fn)
|
|
1412
1413
|
const out = fn(v, { start: pos, end })
|
|
1413
|
-
if (wasCapturing) pushCstLeaf(ctx, {
|
|
1414
|
+
if (wasCapturing) pushCstLeaf(ctx, {
|
|
1415
|
+
_tag: 'leaf',
|
|
1416
|
+
value: code[ip] === OP_SOURCE_LEAF && HOSTCST ? input.slice(pos, end) : out,
|
|
1417
|
+
span: { start: pos, end },
|
|
1418
|
+
})
|
|
1414
1419
|
EC.e = end
|
|
1415
1420
|
return out
|
|
1416
1421
|
}
|
|
@@ -1530,7 +1535,8 @@ function makeDriver(
|
|
|
1530
1535
|
// `attempt()` verbatim: mark, run, and on failure restore every capture
|
|
1531
1536
|
// sink and re-anchor the report at the transaction's entry. A COMMITTED
|
|
1532
1537
|
// failure is still rolled back and then propagated untouched — the
|
|
1533
|
-
// interpreter returns `result` itself on that branch
|
|
1538
|
+
// interpreter returns `result` itself on that branch — unless the row
|
|
1539
|
+
// contains commitment (`k`), where it is reported like any other failure.
|
|
1534
1540
|
const need = rollbackNeeded(ctx)
|
|
1535
1541
|
const mRaw = need ? cstRawLen(ctx) : 0
|
|
1536
1542
|
const mTl = need ? cstTlLen(ctx) : 0
|
|
@@ -1542,7 +1548,10 @@ function makeDriver(
|
|
|
1542
1548
|
const v = exec(code[ip + 1]!, input, pos, ctx)
|
|
1543
1549
|
if (v !== FAIL) return v
|
|
1544
1550
|
if (need) rollbackTriviaAt(ctx, mRaw, mTl, mLv, mFl, mEr, mLog, mRoot)
|
|
1545
|
-
if (ctx._fc === true)
|
|
1551
|
+
if (ctx._fc === true) {
|
|
1552
|
+
if (code[ip + 2] !== 1) return FAIL
|
|
1553
|
+
ctx._fc = false
|
|
1554
|
+
}
|
|
1546
1555
|
ctx._fe = pos
|
|
1547
1556
|
return FAIL
|
|
1548
1557
|
}
|
package/src/table/ops.ts
CHANGED
|
@@ -378,7 +378,9 @@ export const OP_ARMGATE = 36
|
|
|
378
378
|
*/
|
|
379
379
|
export const OP_LIVE = 37
|
|
380
380
|
/**
|
|
381
|
-
* `ATTEMPT c` — `attempt()`, the transactional ordered-choice arm.
|
|
381
|
+
* `ATTEMPT c k` — `attempt()`, the transactional ordered-choice arm. `k` is 1
|
|
382
|
+
* for `attempt(p, { contain: true })`: a committed inner failure is reported
|
|
383
|
+
* uncommitted — `_fc` cleared and `_fe` re-anchored, like any other failure.
|
|
382
384
|
*
|
|
383
385
|
* WAS A TRANSPARENT WRAPPER (`case 'attempt': return this.node(d.parser).ip`),
|
|
384
386
|
* which is correct for exactly one placement — an arm of an `OP_CHOICE`, whose
|
|
@@ -389,10 +391,10 @@ export const OP_LIVE = 37
|
|
|
389
391
|
* re-anchored at the transaction's entry. Both are `attempt()`'s whole contract
|
|
390
392
|
* (`combinators/attempt.ts`), not a choice-arm detail.
|
|
391
393
|
*
|
|
392
|
-
*
|
|
393
|
-
*
|
|
394
|
-
* `
|
|
395
|
-
*
|
|
394
|
+
* No expected-set operand: on a non-committed inner failure the row keeps the
|
|
395
|
+
* inner's `_fx` VERBATIM and only re-anchors `_fe` to `pos`, exactly as
|
|
396
|
+
* `attempt.ts` returns `{ expected: result.expected, span: { start: pos, end:
|
|
397
|
+
* pos } }`. A committed failure propagates untouched when `k` is 0 — the
|
|
396
398
|
* rollback still happens, the re-anchor does not.
|
|
397
399
|
*
|
|
398
400
|
* The interpreter's first-set fail-fast guard is NOT lowered. It is an
|
|
@@ -469,6 +471,8 @@ export const OP_SCOPE_PLAIN = 41
|
|
|
469
471
|
export const OP_LEX_BODY = 42
|
|
470
472
|
/** `LEX_PROGRAM p` — selected fixed composite lexical body, childless. */
|
|
471
473
|
export const OP_LEX_PROGRAM = 43
|
|
474
|
+
/** `SOURCE_LEAF f c` — `sourceLeaf()`: reduce semantically, capture source text. */
|
|
475
|
+
export const OP_SOURCE_LEAF = 44
|
|
472
476
|
/**
|
|
473
477
|
* `DISPATCH sel d other otherRouted n a1 … an` — `dispatch()`.
|
|
474
478
|
*
|
|
@@ -492,7 +496,7 @@ export const OP_NAMES: Record<number, string> = {
|
|
|
492
496
|
[OP_LIT]: 'LIT', [OP_RX]: 'RX', [OP_SEQ]: 'SEQ', [OP_SEQV]: 'SEQV',
|
|
493
497
|
[OP_CHOICE]: 'CHOICE', [OP_REP]: 'REP', [OP_REPV]: 'REPV', [OP_OPT]: 'OPT',
|
|
494
498
|
[OP_XFORM]: 'XFORM', [OP_NODE]: 'NODE', [OP_RULE]: 'RULE', [OP_GATE]: 'GATE',
|
|
495
|
-
[OP_NOT]: 'NOT', [OP_PEEK]: 'PEEK', [OP_LEAF]: 'LEAF', [OP_EMPTY]: 'EMPTY',
|
|
499
|
+
[OP_NOT]: 'NOT', [OP_PEEK]: 'PEEK', [OP_LEAF]: 'LEAF', [OP_SOURCE_LEAF]: 'SOURCE_LEAF', [OP_EMPTY]: 'EMPTY',
|
|
496
500
|
[OP_LIT_TRACK]: 'LIT_TRACK', [OP_RX_TRACK]: 'RX_TRACK', [OP_NODE_TRACK]: 'NODE_TRACK',
|
|
497
501
|
[OP_SCOPE]: 'SCOPE', [OP_EXPECT]: 'EXPECT', [OP_SEQX]: 'SEQX', [OP_SCAN]: 'SCAN',
|
|
498
502
|
[OP_FIELD]: 'FIELD', [OP_LIT_CI]: 'LIT_CI', [OP_LIT_CI_TRACK]: 'LIT_CI_TRACK', [OP_DISPATCH]: 'DISPATCH', [OP_ROUTED]: 'ROUTED',
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
-
OP_ATTEMPT, OP_CHOICE, OP_FIELD, OP_GATE, OP_LABEL, OP_LEAF, OP_LIT, OP_NODE, OP_NOT,
|
|
2
|
+
OP_ATTEMPT, OP_CHOICE, OP_FIELD, OP_GATE, OP_LABEL, OP_LEAF, OP_SOURCE_LEAF, OP_LIT, OP_NODE, OP_NOT,
|
|
3
3
|
OP_PEEK, OP_RULE, OP_RX, OP_SCOPE, OP_SCOPE_CAP, OP_SCOPE_PLAIN, OP_SEQ, OP_SEQV,
|
|
4
4
|
OP_SEQX, OP_TOKEN, OP_XFORM,
|
|
5
5
|
} from './ops.ts'
|
|
@@ -86,7 +86,7 @@ export function leadingScalarTerminal(
|
|
|
86
86
|
continue
|
|
87
87
|
}
|
|
88
88
|
if (op === OP_GATE || op === OP_SCOPE || op === OP_SCOPE_CAP || op === OP_SCOPE_PLAIN
|
|
89
|
-
|| op === OP_XFORM || op === OP_NODE || op === OP_FIELD || op === OP_LEAF) {
|
|
89
|
+
|| op === OP_XFORM || op === OP_NODE || op === OP_FIELD || (op === OP_LEAF || op === OP_SOURCE_LEAF)) {
|
|
90
90
|
at = code[at + 2]!
|
|
91
91
|
depth++
|
|
92
92
|
continue
|
|
@@ -139,7 +139,7 @@ export function leadingScalarSequence(
|
|
|
139
139
|
continue
|
|
140
140
|
}
|
|
141
141
|
if (op === OP_GATE || op === OP_SCOPE_PLAIN
|
|
142
|
-
|| op === OP_XFORM || op === OP_NODE || op === OP_FIELD || op === OP_LEAF) {
|
|
142
|
+
|| op === OP_XFORM || op === OP_NODE || op === OP_FIELD || (op === OP_LEAF || op === OP_SOURCE_LEAF)) {
|
|
143
143
|
at = code[at + 2]!
|
|
144
144
|
continue
|
|
145
145
|
}
|
|
@@ -173,7 +173,7 @@ export function leadingScalarSequence(
|
|
|
173
173
|
at = code[at + 2]!
|
|
174
174
|
continue
|
|
175
175
|
}
|
|
176
|
-
if (op === OP_GATE || op === OP_XFORM || op === OP_NODE || op === OP_FIELD || op === OP_LEAF) {
|
|
176
|
+
if (op === OP_GATE || op === OP_XFORM || op === OP_NODE || op === OP_FIELD || (op === OP_LEAF || op === OP_SOURCE_LEAF)) {
|
|
177
177
|
at = code[at + 2]!
|
|
178
178
|
continue
|
|
179
179
|
}
|
|
@@ -220,7 +220,7 @@ export function leadingLiteralFamily(
|
|
|
220
220
|
return walk(code[at + 1]!, depth + 1)
|
|
221
221
|
}
|
|
222
222
|
if (op === OP_GATE || op === OP_SCOPE || op === OP_SCOPE_CAP || op === OP_SCOPE_PLAIN
|
|
223
|
-
|| op === OP_XFORM || op === OP_NODE || op === OP_FIELD || op === OP_LEAF) {
|
|
223
|
+
|| op === OP_XFORM || op === OP_NODE || op === OP_FIELD || (op === OP_LEAF || op === OP_SOURCE_LEAF)) {
|
|
224
224
|
return walk(code[at + 2]!, depth + 1)
|
|
225
225
|
}
|
|
226
226
|
if (op === OP_SEQ || op === OP_SEQV) {
|
package/src/table/site-labels.ts
CHANGED
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
* label licenses at a leaf is dropping the TEST — never the capture.
|
|
61
61
|
*/
|
|
62
62
|
import {
|
|
63
|
-
OP_ATTEMPT, OP_CHOICE, OP_DISPATCH, OP_EXPECT, OP_FIELD, OP_GATE, OP_LABEL, OP_LEAF,
|
|
63
|
+
OP_ATTEMPT, OP_CHOICE, OP_DISPATCH, OP_EXPECT, OP_FIELD, OP_GATE, OP_LABEL, OP_LEAF, OP_SOURCE_LEAF,
|
|
64
64
|
OP_NODE, OP_NODE_TRACK, OP_NOT, OP_OPT, OP_PEEK, OP_REP, OP_REPV, OP_ROUTED, OP_RULE,
|
|
65
65
|
OP_SCOPE, OP_SCOPE_CAP, OP_SCOPE_PLAIN, OP_SEQ, OP_SEQV, OP_SEQX, OP_TOKEN, OP_XFORM,
|
|
66
66
|
} from './ops.ts'
|
|
@@ -159,8 +159,10 @@ function transfer(code: Int32Array, ip: number, at: SiteLabel, hostCst: boolean)
|
|
|
159
159
|
}
|
|
160
160
|
if (op === OP_NODE || op === OP_NODE_TRACK) {
|
|
161
161
|
const flags = code[ip + 3]!
|
|
162
|
+
// The same selection `emit-assembly.ts` makes: the trailing-trivia bit (128)
|
|
163
|
+
// keeps the children-only shape.
|
|
162
164
|
const directChildren = !hostCst && op === OP_NODE && code[ip + 1]! >= 0 && code[ip + 4]! < 0
|
|
163
|
-
&& (flags === 2 || flags === 18 || flags === 34)
|
|
165
|
+
&& ((flags & ~128) === 2 || flags === 18 || flags === 34)
|
|
164
166
|
if (directChildren) {
|
|
165
167
|
// The confirmed low-arity builder projection installs split child/leaf
|
|
166
168
|
// arrays instead of a CstCaptureBuf. Descendants still capture, but they
|
|
@@ -193,7 +195,7 @@ function transfer(code: Int32Array, ip: number, at: SiteLabel, hostCst: boolean)
|
|
|
193
195
|
if (at.tri === TRI_NONE && !at.buf) return at
|
|
194
196
|
return { tri: TRI_NONE, buf: false, raw: RAW_UNKNOWN, cap: at.cap }
|
|
195
197
|
}
|
|
196
|
-
if (op === OP_LEAF) {
|
|
198
|
+
if (op === OP_LEAF || op === OP_SOURCE_LEAF) {
|
|
197
199
|
if (!at.buf) return at
|
|
198
200
|
return { tri: at.tri, buf: false, raw: RAW_UNKNOWN, cap: at.cap }
|
|
199
201
|
}
|
package/src/types.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { LabeledTriviaSpec } from './cst/trivia-kinds.ts'
|
|
2
|
+
|
|
1
3
|
export type Span = {
|
|
2
4
|
start: number
|
|
3
5
|
end: number
|
|
@@ -56,7 +58,7 @@ export type ParserDef =
|
|
|
56
58
|
// closures live in `gates`.
|
|
57
59
|
| { tag: 'choice'; parsers: Combinator<unknown>[]; gates: (((state: unknown) => boolean) | null)[]; gateSrcs?: (string | null)[]; disjoint: boolean; strategy: ChoiceStrategy; autoNot: (AutoNotCheck[] | null)[] }
|
|
58
60
|
| { tag: 'dispatch'; selector: Combinator<string>; cases: readonly DispatchCase[]; matchers?: readonly DispatchMatcherCase[] | undefined; otherwise?: Combinator<unknown> | undefined; otherwiseUsesRouted?: boolean | undefined }
|
|
59
|
-
| { tag: 'attempt'; parser: Combinator<unknown
|
|
61
|
+
| { tag: 'attempt'; parser: Combinator<unknown>; /** `attempt(p, { contain: true })`: a committed failure inside is reported uncommitted. */ contain?: true }
|
|
60
62
|
// The TAG carries NULLABILITY (what every downstream switch keys on): `many` is
|
|
61
63
|
// the nullable min-0 repeat, `oneOrMore` the non-nullable min>=1 one. `min`/`max`
|
|
62
64
|
// carry the actual ITEM bounds (`many(x, { min: 3, max: 8 })` is a `oneOrMore`
|
|
@@ -79,7 +81,7 @@ export type ParserDef =
|
|
|
79
81
|
// selector matched). It exists so ONE production can serve both contexts instead of
|
|
80
82
|
// being spelled twice — a `routed()` twin and a concrete-lead original.
|
|
81
83
|
| { tag: 'routed'; fallback?: Combinator<unknown> }
|
|
82
|
-
| { tag: 'leaf'; parser: Combinator<unknown>; fn: (v: unknown, span: { start: number; end: number }) => unknown; fnSrc?: string }
|
|
84
|
+
| { tag: 'leaf'; parser: Combinator<unknown>; fn: (v: unknown, span: { start: number; end: number }) => unknown; fnSrc?: string; cstValue?: 'source' }
|
|
83
85
|
| { tag: 'label'; label: string; parser: Combinator<unknown> }
|
|
84
86
|
| { tag: 'field'; name: string; parser: Combinator<unknown> }
|
|
85
87
|
| { tag: 'grammar'; parser: Combinator<unknown>; triviaParser: Combinator<unknown> | undefined; clearTrivia?: boolean; captureTrivia?: boolean; rootCapture?: 'opaque'; trackLines: boolean; constructionTrackLines?: 'on' | 'off' | 'inherit'; constructionCaptureTriviaKinds?: readonly string[] }
|
|
@@ -433,6 +435,11 @@ export type ParserMeta = {
|
|
|
433
435
|
isTrivia: boolean
|
|
434
436
|
/** User-defined labels for labeled trivia arms (`label(name, parser)`). */
|
|
435
437
|
triviaKindLabels?: readonly string[]
|
|
438
|
+
/**
|
|
439
|
+
* Set only by `trivia()`: the labeled-arm spec of this trivia (`null` when it is
|
|
440
|
+
* not labeled), built once so a labeled scan never re-derives it.
|
|
441
|
+
*/
|
|
442
|
+
labeledTriviaSpec?: LabeledTriviaSpec | null
|
|
436
443
|
/** Preclassified lightweight trivia scanner for the ordinary skip path. */
|
|
437
444
|
triviaScanner?: ((input: string, cur: number) => number) | null
|
|
438
445
|
/** Set only by `classifiedTrivia()`: each root-visible category is a separate
|
package/src/version.ts
CHANGED