parseman 0.59.3 → 0.60.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/diagnostics.js +874 -188
- package/dist/analysis/diagnostics.js.map +3 -3
- package/dist/cli/index.js +874 -188
- package/dist/cli/index.js.map +3 -3
- package/dist/combinators/dispatch.d.ts +13 -1
- package/dist/combinators/dispatch.d.ts.map +1 -1
- package/dist/combinators/first-set.d.ts +35 -2
- package/dist/combinators/first-set.d.ts.map +1 -1
- package/dist/combinators/regex.d.ts.map +1 -1
- package/dist/combinators/trivia-boundary.d.ts +11 -0
- package/dist/combinators/trivia-boundary.d.ts.map +1 -1
- package/dist/combinators/trivia-skip.d.ts +1 -1
- package/dist/combinators/trivia-skip.d.ts.map +1 -1
- package/dist/compiler/token-alphabet.d.ts +2 -0
- package/dist/compiler/token-alphabet.d.ts.map +1 -1
- package/dist/compiler/token-capability.js +65 -53
- package/dist/compiler/token-capability.js.map +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +894 -197
- package/dist/index.js.map +3 -3
- package/dist/plugin/evaluator.d.ts.map +1 -1
- package/dist/plugin/index.js +901 -199
- package/dist/plugin/index.js.map +3 -3
- package/dist/regex/first-set.d.ts +19 -0
- package/dist/regex/first-set.d.ts.map +1 -1
- package/dist/table/assemble.d.ts +14 -0
- package/dist/table/assemble.d.ts.map +1 -1
- package/dist/table/child-slots.d.ts.map +1 -1
- package/dist/table/compile-rule-map.d.ts.map +1 -1
- package/dist/table/emit-assembly.d.ts +8 -2
- package/dist/table/emit-assembly.d.ts.map +1 -1
- package/dist/table/emit.d.ts +4 -3
- package/dist/table/emit.d.ts.map +1 -1
- package/dist/table/encode.d.ts.map +1 -1
- package/dist/table/exec.d.ts.map +1 -1
- package/dist/table/factor-choice.d.ts +27 -0
- package/dist/table/factor-choice.d.ts.map +1 -1
- package/dist/table/gap-gate.d.ts +11 -0
- package/dist/table/gap-gate.d.ts.map +1 -0
- package/dist/table/index.js +924 -199
- package/dist/table/index.js.map +3 -3
- package/dist/table/lex-program.d.ts.map +1 -1
- package/dist/table/ops.d.ts +34 -6
- package/dist/table/ops.d.ts.map +1 -1
- package/dist/table/program.d.ts +6 -1
- package/dist/table/program.d.ts.map +1 -1
- package/dist/types.d.ts +16 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
- package/src/analysis/duplication.ts +1 -1
- package/src/combinators/attempt.ts +1 -1
- package/src/combinators/dispatch.ts +30 -6
- package/src/combinators/first-set.ts +55 -12
- package/src/combinators/node.ts +1 -1
- package/src/combinators/regex.ts +9 -4
- package/src/combinators/repeat.ts +6 -6
- package/src/combinators/sequence.ts +1 -1
- package/src/combinators/trivia-boundary.ts +18 -0
- package/src/combinators/trivia-skip.ts +1 -1
- package/src/compiler/ir-serialize.ts +1 -1
- package/src/compiler/token-alphabet.ts +3 -1
- package/src/coverage.ts +1 -1
- package/src/index.ts +1 -1
- package/src/plugin/evaluator.ts +8 -3
- package/src/regex/first-set.ts +37 -3
- package/src/table/assemble.ts +171 -25
- package/src/table/child-slots.ts +8 -3
- package/src/table/compile-rule-map.ts +6 -3
- package/src/table/emit-assembly.ts +182 -31
- package/src/table/emit.ts +6 -4
- package/src/table/encode.ts +468 -73
- package/src/table/exec.ts +38 -13
- package/src/table/factor-choice.ts +62 -0
- package/src/table/fail-clean.ts +2 -2
- package/src/table/gap-gate.ts +55 -0
- package/src/table/hold-gap.ts +2 -2
- package/src/table/lex-program.ts +15 -6
- package/src/table/ops.ts +37 -6
- package/src/table/program.ts +7 -2
- package/src/table/select-hot.ts +7 -2
- package/src/types.ts +12 -5
- package/src/version.ts +1 -1
package/src/table/child-slots.ts
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
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,
|
|
7
|
-
OP_LEX_BODY, OP_LEX_PROGRAM,
|
|
7
|
+
OP_LEX_BODY, OP_LEX_PROGRAM, OP_GAPGATE, OP_RGATE2, OP_STRICT,
|
|
8
8
|
} from './ops.ts'
|
|
9
9
|
|
|
10
10
|
/**
|
|
@@ -47,16 +47,21 @@ export function childSlots(code: ArrayLike<number>, ip: number, out: number[]):
|
|
|
47
47
|
// rest — a coverage-encoded program is a program, and omitting it made
|
|
48
48
|
// `opHistogram` throw on every table the coverage build produces.
|
|
49
49
|
case OP_RULE: case OP_OPT: case OP_NOT: case OP_PEEK: case OP_EXPECT:
|
|
50
|
-
case OP_TOKEN: case OP_ATTEMPT: case OP_LABEL: case OP_COV: case OP_REJECT:
|
|
50
|
+
case OP_TOKEN: case OP_ATTEMPT: case OP_LABEL: case OP_COV: case OP_REJECT: case OP_GAPGATE:
|
|
51
51
|
out.push(code[ip + 1]!)
|
|
52
52
|
return true
|
|
53
53
|
|
|
54
54
|
// Single child at `ip + 2`.
|
|
55
55
|
case OP_GATE: case OP_SCOPE: case OP_SCOPE_CAP: case OP_SCOPE_PLAIN: case OP_WITHCTX: case OP_XFORM:
|
|
56
|
-
case OP_NODE: case OP_NODE_TRACK: case OP_FIELD: case OP_LEAF: case OP_SOURCE_LEAF: case OP_ARMGATE: case OP_RGATE:
|
|
56
|
+
case OP_NODE: case OP_NODE_TRACK: case OP_FIELD: case OP_LEAF: case OP_SOURCE_LEAF: case OP_ARMGATE: case OP_RGATE: case OP_RGATE2:
|
|
57
57
|
out.push(code[ip + 2]!)
|
|
58
58
|
return true
|
|
59
59
|
|
|
60
|
+
// `STRICT strict tolerant` — both encodings are reachable; an assembly keeps one.
|
|
61
|
+
case OP_STRICT:
|
|
62
|
+
out.push(code[ip + 1]!, code[ip + 2]!)
|
|
63
|
+
return true
|
|
64
|
+
|
|
60
65
|
// `ROUTED fallback` — the operand is an offset or −1. The routed token itself is
|
|
61
66
|
// data the enclosing `dispatch()` already consumed, so the only edge is the
|
|
62
67
|
// fallback.
|
|
@@ -356,9 +356,12 @@ export function compileRuleMap(
|
|
|
356
356
|
const roots = assemblyExtraIps(artifact)
|
|
357
357
|
let siteBytes, fixed
|
|
358
358
|
try {
|
|
359
|
-
//
|
|
360
|
-
//
|
|
361
|
-
|
|
359
|
+
// RANKED with every gap gate lowered as its item (`gapNeutral`), so the
|
|
360
|
+
// gates move no site's place in the order; the fit below is judged by the
|
|
361
|
+
// source that ships, gates included. Priced throughout without the
|
|
362
|
+
// `_errors` marks a lexical program over a scan adds (`scanSinkNeutral`),
|
|
363
|
+
// so they move no site in or out of the budget.
|
|
364
|
+
siteBytes = emitAssemblySource(table, artifact, cfg, roots, true, undefined, true, true).siteBytes
|
|
362
365
|
// What any selective assembly costs before its first site body: the shared
|
|
363
366
|
// prelude and the factory's tail. A budget that cannot cover it buys nothing.
|
|
364
367
|
fixed = emitAssemblySource(table, artifact, cfg, roots, true, new Set(), true).source.length
|
|
@@ -47,11 +47,11 @@
|
|
|
47
47
|
import type { ParseContext } from '../types.ts'
|
|
48
48
|
import { regexCanMatchEmpty } from '../regex/first-set.ts'
|
|
49
49
|
import {
|
|
50
|
-
OP_ADJ, OP_ARMGATE, OP_RGATE, OP_ATTEMPT, OP_CHOICE, OP_DISPATCH, OP_EMPTY, OP_EXPECT, OP_FIELD, OP_GATE,
|
|
50
|
+
OP_ADJ, OP_ARMGATE, OP_RGATE, OP_RGATE2, OP_ATTEMPT, OP_CHOICE, OP_DISPATCH, OP_EMPTY, OP_EXPECT, OP_FIELD, OP_GATE,
|
|
51
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_WITHCTX, OP_XFORM,
|
|
54
|
-
OP_LEX_BODY, OP_LEX_PROGRAM,
|
|
54
|
+
OP_LEX_BODY, OP_LEX_PROGRAM, OP_GAPGATE, OP_STRICT,
|
|
55
55
|
} from './ops.ts'
|
|
56
56
|
import {
|
|
57
57
|
choiceCannotCommit, choiceRollbackMask, failureRollbackClean, validateDispatchSpec,
|
|
@@ -67,10 +67,16 @@ import {
|
|
|
67
67
|
scalarTerminalNodeChild, scalarTerminalNotChild, scalarTerminalPeekChild, type LeadingScalarSequence,
|
|
68
68
|
} from './scalar-terminal.ts'
|
|
69
69
|
import { childSlots } from './child-slots.ts'
|
|
70
|
-
import { holdsGap } from './hold-gap.ts'
|
|
70
|
+
import { holdsGap, rowNullable } from './hold-gap.ts'
|
|
71
|
+
import { gapRows } from './gap-gate.ts'
|
|
71
72
|
import { fieldSpec, leadingTriviaFieldAt } from './field-spec.ts'
|
|
72
73
|
import { FIELD_LEADING_TRIVIA, FIELD_TRAILING_TRIVIA } from '../combinators/field-trivia.ts'
|
|
73
74
|
|
|
75
|
+
/** Does a gap's regex lower to an inline scan (`scanShapeFromRegex`)? */
|
|
76
|
+
function gapRegexLowers(re: unknown): boolean {
|
|
77
|
+
return re instanceof RegExp && scanShapeFromRegex(re.source, re.flags) !== null
|
|
78
|
+
}
|
|
79
|
+
|
|
74
80
|
/** What the compiled factory hands back — the emitted twin of `Assembly`. */
|
|
75
81
|
export type EmittedPiece = (input: string, pos: number, ctx: ParseContext) => unknown
|
|
76
82
|
export type EmittedAssembly = {
|
|
@@ -109,12 +115,12 @@ export class Unemittable extends Error {
|
|
|
109
115
|
* runs and is wrong, which no type in this file would catch.
|
|
110
116
|
*/
|
|
111
117
|
export const EMITTED_PARAMS = [
|
|
112
|
-
'EC', 'FAIL', 'K', 'FX', 'FNS', 'MASK', 'CLS', 'AFX', 'TRIVIA', '
|
|
118
|
+
'EC', 'FAIL', 'K', 'FX', 'FNS', 'MASK', 'CLS', 'AFX', 'TRIVIA', 'TRIVIASCAN',
|
|
113
119
|
'SCANS', 'DISP', 'DSP', 'EMPTY_FX', 'EMPTY_CH', 'EMPTY_TLOG', 'EMPTY_TL',
|
|
114
120
|
'cstCaptureActive', 'pushCstLeaf', 'pushCstChild', 'rollbackTriviaAt', 'rollbackScannedTriviaAt', 'failAt',
|
|
115
121
|
'classHas', 'consumeTrivia', 'buildFieldMap', 'projectChild', 'unwrapChild',
|
|
116
122
|
'demoteCapturedToRaw', 'cstLeavesLen', 'skipTriviaScanned', 'needsDeferredTriviaCommit',
|
|
117
|
-
'
|
|
123
|
+
'advanceTrivia', 'refuseUnclassifiedRootScope', 'spanLines', 'rawEntry', 'lead',
|
|
118
124
|
'asciiFoldKey', 'ROUTED_FX',
|
|
119
125
|
// RECOVERY, bound rather than reimplemented. `SENTS` is the sync sentinel per
|
|
120
126
|
// char-class index — the emitted twin of `assemble.ts`'s `sentinelFor` memo,
|
|
@@ -124,27 +130,24 @@ export const EMITTED_PARAMS = [
|
|
|
124
130
|
// engine and cannot drift.
|
|
125
131
|
'SENTS', 'matchesAt', 'recoverScan', 'orSentinel', 'captureError',
|
|
126
132
|
// Pure scalar terminal recognizers, indexed by the terminal's existing const
|
|
127
|
-
// operand
|
|
128
|
-
'RECOG',
|
|
129
|
-
//
|
|
130
|
-
// positional helper binding. Older factories ignore this trailing argument.
|
|
131
|
-
'commitTriviaScan', 'scanTriviaCompact', 'LEX',
|
|
132
|
-
// Appended for old precompiled-factory ABI: old factories ignore it.
|
|
133
|
-
'adjacencyHolds',
|
|
134
|
-
// Appended for selected composite lexical programs; old factories ignore it.
|
|
133
|
+
// operand; selected lexical bodies; the adjacency test.
|
|
134
|
+
'RECOG', 'LEX', 'adjacencyHolds',
|
|
135
|
+
// Selected composite lexical programs.
|
|
135
136
|
'LEXPROG',
|
|
136
|
-
//
|
|
137
|
-
// old factories ignore them.
|
|
137
|
+
// Trivia-attributing fields (`field(…, { leadingTrivia })`).
|
|
138
138
|
'LEAD', 'triviaFieldCapture',
|
|
139
|
-
//
|
|
140
|
-
// factories ignore it.
|
|
139
|
+
// The deferred-commit trivia skip (`trivia-gap.ts`).
|
|
141
140
|
'skipDeferredTrivia',
|
|
142
|
-
//
|
|
143
|
-
// ignore it.
|
|
141
|
+
// The pass-through collapse (`run-support.ts`).
|
|
144
142
|
'collapseEmpty',
|
|
145
|
-
//
|
|
146
|
-
//
|
|
143
|
+
// Constructor-built tree objects (`cst/plain.d.ts`): a `new` call has no
|
|
144
|
+
// allocation site, so V8 cannot pretenure them.
|
|
147
145
|
'_SP', '_LF', '_ND', '_FC', '_BUF',
|
|
146
|
+
// The gap gate's slot (`gap-gate.ts`).
|
|
147
|
+
'GAP',
|
|
148
|
+
// Artifacts are version-locked (docs/design/artifact-format.md): a factory is
|
|
149
|
+
// only ever called by the runtime that emitted it, so a name nothing emitted
|
|
150
|
+
// reads is removed rather than kept for an older factory's positions.
|
|
148
151
|
] as const
|
|
149
152
|
|
|
150
153
|
/**
|
|
@@ -605,6 +608,12 @@ export function emitAssemblySource(
|
|
|
605
608
|
* then emitted with them.
|
|
606
609
|
*/
|
|
607
610
|
scanSinkNeutral = false,
|
|
611
|
+
/**
|
|
612
|
+
* Lower every gap gate (`gap-gate.ts`) as the item it wraps. A selection is
|
|
613
|
+
* ranked on this lowering, so adding the gates moves no site's place in the
|
|
614
|
+
* order; the fit is judged by the source that ships, gates included.
|
|
615
|
+
*/
|
|
616
|
+
gapNeutral = false,
|
|
608
617
|
): EmitResult {
|
|
609
618
|
const { code, k, fx, disp, dsp, triviaLabelled } = t
|
|
610
619
|
const swapLegal = !cfg.trackLines
|
|
@@ -643,6 +652,10 @@ export function emitAssemblySource(
|
|
|
643
652
|
// emitted scope supplies a context this pass cannot see.
|
|
644
653
|
const roots = [...Object.values(prog.rules), ...extraIps]
|
|
645
654
|
const reachable = [...reachableSites(code, roots)]
|
|
655
|
+
// A gap whose regex lowers to an inline scan is scanned again by its row, for
|
|
656
|
+
// less than reading the slot costs; only a gap matched by its RegExp takes the
|
|
657
|
+
// slot (`gapRegexLowers`).
|
|
658
|
+
const GAP_ROWS = gapNeutral ? new Set<number>() : new Set([...gapRows(code, roots)].filter(row => !gapRegexLowers(k[code[row + 1]!])))
|
|
646
659
|
const labels = computeSiteLabels(code, roots, hostCst)
|
|
647
660
|
// Eligibility is pooled by the terminal's existing constant operand, matching
|
|
648
661
|
// the closure recognizer pool: distinct terminal rows sharing one spec share
|
|
@@ -1172,7 +1185,8 @@ ${cfg.probe ? `failAt(ctx,${xf},pos)\n` : ''}return FAIL
|
|
|
1172
1185
|
// `OP_GATE` under a probe or a tolerant recovery is a no-op that forwards to
|
|
1173
1186
|
// its child, exactly as `assemble.ts:952` resolves it.
|
|
1174
1187
|
if (op === OP_GATE && (cfg.tolerant || cfg.probe)) return code[ip + 2]!
|
|
1175
|
-
if (op === OP_RULE) return code[ip + 1]!
|
|
1188
|
+
if (op === OP_RULE || op === OP_GAPGATE) return code[ip + 1]!
|
|
1189
|
+
if (op === OP_STRICT) return code[cfg.tolerant || cfg.probe ? ip + 2 : ip + 1]!
|
|
1176
1190
|
// A SCOPE THAT INSTALLS WHAT IS ALREADY INSTALLED. `encode.ts:520` wraps
|
|
1177
1191
|
// EVERY rule of a `rules({ trivia }, …)` map in its own `OP_SCOPE_PLAIN`, so a
|
|
1178
1192
|
// grammar with one ambient trivia re-installs the same slot at every rule
|
|
@@ -1191,6 +1205,7 @@ ${cfg.probe ? `failAt(ctx,${xf},pos)\n` : ''}return FAIL
|
|
|
1191
1205
|
function lower(ip: number, fname: string): string {
|
|
1192
1206
|
const op = code[ip]
|
|
1193
1207
|
const head = `function ${fname}(input,pos,ctx){`
|
|
1208
|
+
const outerHead = head
|
|
1194
1209
|
const L = labels.at(ip)
|
|
1195
1210
|
const sinks = sinksAt(ip)
|
|
1196
1211
|
/**
|
|
@@ -1310,6 +1325,26 @@ ${cfg.probe ? `failAt(ctx,${xf},pos)\n` : ''}return FAIL
|
|
|
1310
1325
|
case OP_RX_TRACK: {
|
|
1311
1326
|
const xf = fxRef(code[ip + 2]!)
|
|
1312
1327
|
const track = op === OP_RX_TRACK
|
|
1328
|
+
// A gap row takes the end a repeat already scanned (`gap-gate.ts`), and
|
|
1329
|
+
// otherwise matches its sticky regex: one compact body, no second copy of
|
|
1330
|
+
// the success path.
|
|
1331
|
+
if (GAP_ROWS.has(ip)) {
|
|
1332
|
+
const re = hoist('gx', `K[${code[ip + 1]!}]`)
|
|
1333
|
+
return `${outerHead}
|
|
1334
|
+
let e
|
|
1335
|
+
if(GAP.row===${ip}&&GAP.at===pos&&GAP.input===input)e=GAP.end
|
|
1336
|
+
else{${re}.lastIndex=pos;e=${re}.test(input)?${re}.lastIndex:-1}
|
|
1337
|
+
if(e>=0){
|
|
1338
|
+
const v=input.slice(pos,e)
|
|
1339
|
+
${captureLeaf('v')}
|
|
1340
|
+
${track ? '_trackLines(ctx,input,e)\n' : ''}EC.e=e
|
|
1341
|
+
return v
|
|
1342
|
+
}
|
|
1343
|
+
ctx._fe=pos;ctx._fx=${xf}
|
|
1344
|
+
${cfg.probe ? `failAt(ctx,${xf},pos)\n` : ''}return FAIL
|
|
1345
|
+
}`
|
|
1346
|
+
}
|
|
1347
|
+
const head = outerHead
|
|
1313
1348
|
if (!track && scalarSpecs.has(code[ip + 1]!)) {
|
|
1314
1349
|
const recognize = recognizerRef(code[ip + 1]!)
|
|
1315
1350
|
return `${head}
|
|
@@ -1401,6 +1436,31 @@ return ${child}(input,pos,ctx)
|
|
|
1401
1436
|
}`
|
|
1402
1437
|
}
|
|
1403
1438
|
|
|
1439
|
+
case OP_RGATE2: {
|
|
1440
|
+
const child = link(code[ip + 2]!)
|
|
1441
|
+
if (cfg.tolerant || cfg.probe) return `${head}
|
|
1442
|
+
return ${child}(input,pos,ctx)
|
|
1443
|
+
}`
|
|
1444
|
+
// ASCII-only classes (the usual case: `:` then whitespace) are read as
|
|
1445
|
+
// code units against their ASCII tables, with no call.
|
|
1446
|
+
const idxs = [code[ip + 1]!, code[ip + 4]!, code[ip + 5]!]
|
|
1447
|
+
const ascii = idxs.every(i => isAsciiOnlyClass(t.cc[i]!))
|
|
1448
|
+
const ref = (idx: number, tag: string): string => {
|
|
1449
|
+
const ci = classes.push([t.cc[idx]!]) - 1
|
|
1450
|
+
classPlan.push([idx])
|
|
1451
|
+
return hoist(tag, `CLS[${ci}][0]`)
|
|
1452
|
+
}
|
|
1453
|
+
const [cls, first, second] = [ref(idxs[0]!, 'cl'), ref(idxs[1]!, 'c1'), ref(idxs[2]!, 'c2')]
|
|
1454
|
+
const has = (c: string, at: string): string => ascii ? `${c}.ascii[${at}]===1` : `classHas(${c},${at})`
|
|
1455
|
+
const xf = fxRef(code[ip + 3]!)
|
|
1456
|
+
return `${head}
|
|
1457
|
+
const it=ctx._routed
|
|
1458
|
+
if(it!==undefined&&it.span.start===pos){const e=it.span.end;const c=${ascii ? 'input.charCodeAt(e)' : 'lead(input,e)'}
|
|
1459
|
+
if(!(${has(cls, 'c')})||(${has(first, 'c')}&&!(${has(second, ascii ? 'input.charCodeAt(e+1)' : 'lead(input,e+1)')}))){ctx._fe=e;ctx._fx=${xf};return FAIL}}
|
|
1460
|
+
return ${child}(input,pos,ctx)
|
|
1461
|
+
}`
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1404
1464
|
case OP_ARMGATE: {
|
|
1405
1465
|
// `choice({ gate, combinator })`, as `assemble.ts` lowers it. A blocked
|
|
1406
1466
|
// arm is SKIPPED, not failed (choice.ts `continue`): it clears the cut,
|
|
@@ -1914,7 +1974,30 @@ if(ctx._fc===true)return FAIL
|
|
|
1914
1974
|
const af=ctx._fx
|
|
1915
1975
|
if(af!==undefined&&af.length>0){ctx._fe=pos;return FAIL}
|
|
1916
1976
|
}
|
|
1917
|
-
|
|
1977
|
+
${cfg.probe ? `if(arm<0){${arms.map(a => `${a}(input,pos,ctx)`).join(';')}}
|
|
1978
|
+
` : ''}ctx._fe=pos;ctx._fx=${choiceFx}
|
|
1979
|
+
return FAIL
|
|
1980
|
+
}`
|
|
1981
|
+
}
|
|
1982
|
+
|
|
1983
|
+
if (cfg.probe) {
|
|
1984
|
+
// A probe assembly enters every arm in source order, as the
|
|
1985
|
+
// interpreter's `firstMatch` does, so each opener reaches `failAt`.
|
|
1986
|
+
const p = tmp()
|
|
1987
|
+
return `${head}
|
|
1988
|
+
${emitMark(p, L.buf, L.raw, sinks)}
|
|
1989
|
+
let acc
|
|
1990
|
+
let best=pos
|
|
1991
|
+
${arms.map(arm => `{ctx._fc=false
|
|
1992
|
+
const v=${arm}(input,pos,ctx)
|
|
1993
|
+
if(v!==FAIL)return v
|
|
1994
|
+
const at=ctx._fe??pos
|
|
1995
|
+
if(at>best){best=at;acc=undefined}
|
|
1996
|
+
if(at===best)acc=_accSet(ctx._fx,acc)
|
|
1997
|
+
if(ctx._fc===true){if(acc!==undefined)ctx._fx=acc;return FAIL}
|
|
1998
|
+
${emitRollback(p, L.buf, L.raw, sinks)}
|
|
1999
|
+
}`).join('\n')}
|
|
2000
|
+
ctx._fe=pos;ctx._fx=acc??${choiceFx}
|
|
1918
2001
|
return FAIL
|
|
1919
2002
|
}`
|
|
1920
2003
|
}
|
|
@@ -2194,7 +2277,8 @@ return FAIL
|
|
|
2194
2277
|
const selector = link(code[ip + 1]!)
|
|
2195
2278
|
const otherIp = code[ip + 3]!
|
|
2196
2279
|
const other = otherIp >= 0 ? link(otherIp) : undefined
|
|
2197
|
-
const otherRouted = code[ip + 4]! === 1
|
|
2280
|
+
const otherRouted = (code[ip + 4]! & 1) === 1
|
|
2281
|
+
const otherCommits = (code[ip + 4]! & 2) === 0
|
|
2198
2282
|
const n = code[ip + 5]!
|
|
2199
2283
|
validateDispatchSpec(spec, n, code[ip + 4]!)
|
|
2200
2284
|
const armBase = ip + 6
|
|
@@ -2258,7 +2342,7 @@ ${fallbackCase}
|
|
|
2258
2342
|
}
|
|
2259
2343
|
if(v===FAIL){
|
|
2260
2344
|
${emitRollback(m2, L.buf, L.raw, sinks)}
|
|
2261
|
-
ctx._fc=true
|
|
2345
|
+
${other !== undefined && !otherCommits ? 'if(arm!==undefined)' : ''}ctx._fc=true
|
|
2262
2346
|
return FAIL
|
|
2263
2347
|
}
|
|
2264
2348
|
return [key,v]
|
|
@@ -2293,6 +2377,69 @@ return [key,v]
|
|
|
2293
2377
|
const itemMiss = (at: string): string => itemAscii
|
|
2294
2378
|
? `${itemCls}.ascii[input.charCodeAt(${at})]!==1`
|
|
2295
2379
|
: `!classHas(${itemCls},lead(input,${at}))`
|
|
2380
|
+
// The separator's own first set (ip+7, `assemble.ts` `sepClass`).
|
|
2381
|
+
let sepCls: string | undefined
|
|
2382
|
+
let sepAscii = false
|
|
2383
|
+
if (!REC && sepIp >= 0 && (code[ip + 5]! & 8) !== 0 && !leadingTriviaFieldAt(code, k, sepIp) && !rowNullable(code, k, sepIp, nullable)) {
|
|
2384
|
+
const resolved = t.cc[code[ip + 8]!]!
|
|
2385
|
+
const ci = classes.push([resolved]) - 1
|
|
2386
|
+
classPlan.push([code[ip + 8]!])
|
|
2387
|
+
sepCls = hoist('rs', `CLS[${ci}][0]`)
|
|
2388
|
+
sepAscii = isAsciiOnlyClass(resolved)
|
|
2389
|
+
}
|
|
2390
|
+
// THE GAP GATE (`gap-gate.ts`, `assemble.ts`): the item's leading gap is
|
|
2391
|
+
// read once here and handed to its regex row through the slot.
|
|
2392
|
+
const gapIp = !gapNeutral && sepIp < 0 && min === 0 && !REC && code[code[ip + 1]!] === OP_GAPGATE ? code[ip + 1]! : -1
|
|
2393
|
+
let gapCode = (_rb: string): string => ''
|
|
2394
|
+
if (gapIp >= 0) {
|
|
2395
|
+
const row = code[gapIp + 2]!
|
|
2396
|
+
const rx = k[code[row + 1]!]
|
|
2397
|
+
const shape = gapRegexLowers(rx) ? scanShapeFromRegex((rx as RegExp).source, (rx as RegExp).flags) : null
|
|
2398
|
+
const cls = (idx: number): string => {
|
|
2399
|
+
const resolved = t.cc[idx]!
|
|
2400
|
+
const ci = classes.push([resolved]) - 1
|
|
2401
|
+
classPlan.push([idx])
|
|
2402
|
+
const ref = hoist('gc', `CLS[${ci}][0]`)
|
|
2403
|
+
return isAsciiOnlyClass(resolved) ? `${ref}.ascii[input.charCodeAt(${'@'})]!==1` : `!classHas(${ref},lead(input,${'@'}))`
|
|
2404
|
+
}
|
|
2405
|
+
const follow = cls(code[gapIp + 3]!).replaceAll('@', 'gq')
|
|
2406
|
+
const other = code[gapIp + 4]! >= 0 ? cls(code[gapIp + 4]!).replaceAll('@', 'itemStart') : 'true'
|
|
2407
|
+
if (shape !== null) {
|
|
2408
|
+
// Scanned inline, as the row scans it: no slot to fill or read.
|
|
2409
|
+
let n = 0
|
|
2410
|
+
const q = tmp()
|
|
2411
|
+
const m = emitShapeMatch(shape, 'itemStart', (prefix = '_v') => `${q}${prefix}${n++}`, '')
|
|
2412
|
+
gapCode = (rbCode: string): string => `let gapped=false
|
|
2413
|
+
if(ctx._probe===undefined){
|
|
2414
|
+
${m.setup.join('\n')}
|
|
2415
|
+
const gq=${m.ok}?${m.end}:-1
|
|
2416
|
+
if((gq<0||${follow})&&${other}){
|
|
2417
|
+
${rbCode}
|
|
2418
|
+
break
|
|
2419
|
+
}
|
|
2420
|
+
}
|
|
2421
|
+
`
|
|
2422
|
+
} else {
|
|
2423
|
+
const re = hoist('gr', `K[${code[row + 1]!}]`)
|
|
2424
|
+
gapCode = (rbCode: string): string => `let gapped=false
|
|
2425
|
+
if(ctx._probe===undefined){
|
|
2426
|
+
${re}.lastIndex=itemStart
|
|
2427
|
+
const gq=${re}.test(input)?${re}.lastIndex:-1
|
|
2428
|
+
if((gq<0||${follow})&&${other}){
|
|
2429
|
+
${rbCode}
|
|
2430
|
+
break
|
|
2431
|
+
}
|
|
2432
|
+
if(gq>=0){
|
|
2433
|
+
GAP.row=${row};GAP.at=itemStart;GAP.end=gq;GAP.input=input
|
|
2434
|
+
gapped=true
|
|
2435
|
+
}
|
|
2436
|
+
}
|
|
2437
|
+
`
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2440
|
+
const sepMiss = (at: string): string => sepAscii
|
|
2441
|
+
? `${sepCls}.ascii[input.charCodeAt(${at})]!==1`
|
|
2442
|
+
: `!classHas(${sepCls},lead(input,${at}))`
|
|
2296
2443
|
const skipBeforeFirst = sepIp < 0 && min === 0
|
|
2297
2444
|
const p = tmp()
|
|
2298
2445
|
// `viaRepItem` is `sep === undefined && count >= min && (count > 0 ||
|
|
@@ -2387,7 +2534,7 @@ ${p}rt=ctx._rootTriviaLog!==undefined?ctx._rootTriviaLog.length:0${markSinks}
|
|
|
2387
2534
|
// is taken unconditionally by the closure engine (`assemble.ts:2067`), so
|
|
2388
2535
|
// it is resolved separately from `itemFx` — reusing that one would hand
|
|
2389
2536
|
// `EMPTY_FX` to every recovered error in a grammar that does not report.
|
|
2390
|
-
const recFx = REC ? fxRef(code[ip + 6]!)
|
|
2537
|
+
const recFx = !REC ? '' : (flags & 16) !== 0 ? '[]' : fxRef(code[ip + 6]!)
|
|
2391
2538
|
const sepSent = REC ? sentRef(code[ip + 7]!) : 'undefined'
|
|
2392
2539
|
const recSent = sep === undefined
|
|
2393
2540
|
? my
|
|
@@ -2405,7 +2552,7 @@ continue
|
|
|
2405
2552
|
`
|
|
2406
2553
|
return `${head}
|
|
2407
2554
|
const out=${collect ? '[]' : 'undefined'}
|
|
2408
|
-
${knownTrivia === undefined ? 'const hasTrivia=ctx.trivia!==undefined\n' : ''}${L.buf ? '' : 'const needMark=_rollbackNeeded(ctx)\n'}${REC ? `const ${my}=ctx._sync\n` : ''}${itemCls === undefined ? '' : `const ${p}gate=ctx._probe===undefined\n`}let cur=pos
|
|
2555
|
+
${knownTrivia === undefined ? 'const hasTrivia=ctx.trivia!==undefined\n' : ''}${L.buf ? '' : 'const needMark=_rollbackNeeded(ctx)\n'}${REC ? `const ${my}=ctx._sync\n` : ''}${itemCls === undefined && sepCls === undefined ? '' : `const ${p}gate=ctx._probe===undefined\n`}let cur=pos
|
|
2409
2556
|
let count=0
|
|
2410
2557
|
for(;;){
|
|
2411
2558
|
${max >= 0 ? `if(count>=${max})break\n` : ''}${sep !== undefined ? `if(count>0&&count>=${min}&&cur>=input.length)break\n` : ''}${itemCls !== undefined && sep === undefined ? `if(count>=${min}&&${p}gate&&${hasTrivia === 'false' ? 'true' : hasTrivia === 'true' ? 'false' : '!hasTrivia'}&&${itemMiss('cur')})break\n` : ''}let ${p}raw=0,${p}tl=0,${p}lv=0,${p}lg=0,${p}rt=0${sinks.fd ? `,${p}fd=0` : ''}${sinks.er ? `,${p}er=0` : ''}
|
|
@@ -2415,7 +2562,11 @@ let sepEnd=-1
|
|
|
2415
2562
|
${sep !== undefined ? `if(count>0){
|
|
2416
2563
|
const lb=cstLeavesLen(ctx)
|
|
2417
2564
|
let sp=cur
|
|
2418
|
-
${hasTrivia === 'false' ? '' : hasTrivia === 'true' ? `sp=${skip}(input,sp,ctx)\n` : `if(hasTrivia)sp=${skip}(input,sp,ctx)\n`}
|
|
2565
|
+
${hasTrivia === 'false' ? '' : hasTrivia === 'true' ? `sp=${skip}(input,sp,ctx)\n` : `if(hasTrivia)sp=${skip}(input,sp,ctx)\n`}${sepCls === undefined ? '' : `if(count>=${min}&&${p}gate&&${sepMiss('sp')}){
|
|
2566
|
+
${rb}
|
|
2567
|
+
break
|
|
2568
|
+
}
|
|
2569
|
+
`}ctx._fc=false
|
|
2419
2570
|
${sepIp >= 0 && leadingTriviaFieldAt(code, k, sepIp) ? 'LEAD.at=cur\n' : ''}const sv=${sep}(input,sp,ctx)
|
|
2420
2571
|
if(sv===FAIL){
|
|
2421
2572
|
${rb}
|
|
@@ -2430,13 +2581,13 @@ itemStart=${hasTrivia === 'false' ? 'EC.e' : hasTrivia === 'true' ? `${skip}(inp
|
|
|
2430
2581
|
${rb}
|
|
2431
2582
|
${trailingAllowed ? 'if(sepEnd>=0)cur=sepEnd\n' : ''}break
|
|
2432
2583
|
}
|
|
2433
|
-
${itemCls === undefined ? '' : `if(count>=${min}&&${p}gate&&${itemMiss('itemStart')}){
|
|
2584
|
+
${itemCls === undefined || (sep === undefined && hasTrivia === 'false') ? '' : `if(count>=${min}&&${p}gate&&${sep === undefined && hasTrivia === 'hasTrivia' ? 'hasTrivia&&' : ''}${itemMiss('itemStart')}){
|
|
2434
2585
|
${rb}
|
|
2435
2586
|
${trailingAllowed ? 'if(sepEnd>=0)cur=sepEnd\n' : ''}break
|
|
2436
2587
|
}
|
|
2437
|
-
`}ctx._fc=false
|
|
2588
|
+
`}${gapCode(rb)}ctx._fc=false
|
|
2438
2589
|
const v=${child}(input,itemStart,ctx)
|
|
2439
|
-
if(v===FAIL){
|
|
2590
|
+
${gapIp >= 0 ? 'if(gapped)GAP.at=-1\n' : ''}if(v===FAIL){
|
|
2440
2591
|
${REC
|
|
2441
2592
|
// THE ROLLBACK MOVES INSIDE THE ARMS. The strict loop rolls back
|
|
2442
2593
|
// unconditionally before testing `_fc`, which is sound because every path out
|
package/src/table/emit.ts
CHANGED
|
@@ -6,11 +6,12 @@ import { cfgKey, type RunCfg } from './assemble.ts'
|
|
|
6
6
|
import { PARSEMAN_VERSION } from '../version.ts'
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
* Opens every printed assembly factory.
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* Opens every printed assembly factory. Helpers are bound BY POSITION, so a runtime that
|
|
10
|
+
* passes any other number of them, fewer or more, binds the wrong function to a name;
|
|
11
|
+
* refuse it when the assembly is built, instead of failing partway through the first parse
|
|
12
|
+
* on whichever helper is reached first. Runs once per assembly.
|
|
12
13
|
*/
|
|
13
|
-
export const FACTORY_ARITY_GUARD = `if(arguments.length
|
|
14
|
+
export const FACTORY_ARITY_GUARD = `if(arguments.length!==${EMITTED_PARAMS.length})throw new Error(${JSON.stringify(`parseman: this parser was printed by parseman ${PARSEMAN_VERSION}; run it on parseman ${PARSEMAN_VERSION} or reprint it`)});`
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* Print a program as the module a build emits.
|
|
@@ -229,6 +230,7 @@ function emitAssemblies(prog: TableProgram, cfgs: readonly AssemblyCfg[]): strin
|
|
|
229
230
|
const plan = em.plan
|
|
230
231
|
out.push('{'
|
|
231
232
|
+ `key:${key},`
|
|
233
|
+
+ `arity:${EMITTED_PARAMS.length},`
|
|
232
234
|
+ `factory:function(${EMITTED_PARAMS.join(',')}){${FACTORY_ARITY_GUARD}${em.source}\n},`
|
|
233
235
|
+ 'plan:{'
|
|
234
236
|
+ `classes:[${plan.classes.map(r => `[${r.join(',')}]`).join(',')}],`
|