parseman 0.60.0 → 0.60.2

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.
@@ -1,557 +0,0 @@
1
- import type { ParseContext, ParseResult } from '../types.ts'
2
- import { advanceTrivia, commitTriviaScan, needsDeferredTriviaCommit, rollbackTrivia, saveTriviaMark, scanTriviaCompact } from '../combinators/trivia-skip.ts'
3
- import {
4
- beginCstNodeCapture, cstCaptureActive, endCstNodeCapture, pushCstChild,
5
- pushCstLeaf, rollbackCstCapture, saveCstMark, type CstRollbackMark,
6
- } from '../cst/capture-buffer.ts'
7
- import {
8
- OP_CHOICE, OP_EMPTY, OP_GATE, OP_LEAF, OP_SOURCE_LEAF, OP_LIT, OP_NODE, OP_NOT, OP_OPT,
9
- OP_PEEK, OP_REP, OP_REPV, OP_RULE, OP_RX, OP_SEQ, OP_SEQV, OP_XFORM,
10
- OP_LIT_TRACK, OP_RX_TRACK, OP_NODE_TRACK, OP_SCOPE, OP_EXPECT,
11
- } from './ops.ts'
12
- import {
13
- expandCompact, resolveTable,
14
- type CompactProgram, type ResolvedClass, type ResolvedDispatch, type TableProgram, type TableRule,
15
- } from './program.ts'
16
-
17
- /**
18
- * THE SHARED DRIVER.
19
- *
20
- * One recognizer for every grammar, every rule and every variant — the thing
21
- * codegen emits once PER RULE, and which therefore sets the per-rule byte floor.
22
- * It uses the same zero-allocation protocol the emitted code uses (a sentinel
23
- * for failure plus a shared end-position slot), so what it adds over open-coded
24
- * recognition is the opcode read and the switch, not an allocation.
25
- *
26
- * The driver is instantiated ONCE PER RESOLVED TABLE, closing over that table's
27
- * arrays. Instruction operands are then reads of context slots rather than
28
- * property loads through an object, and — the point of G5 — an option can never
29
- * be consulted here: two settings pairs are two tables, not two code paths.
30
- */
31
-
32
- /** Failure sentinel — identity-compared, never inspected. */
33
- const FAIL: unique symbol = Symbol('pm.fail')
34
-
35
- const EMPTY_TL: readonly number[] = Object.freeze([])
36
- const EMPTY_FX: string[] = []
37
-
38
- type Leaf = { _tag: 'leaf'; value: string; span: { start: number; end: number } }
39
-
40
- function rawEntry(v: unknown, input: string, s: number, e: number): unknown {
41
- if (typeof v === 'object' && v !== null) {
42
- const t = (v as { _tag?: string })._tag
43
- if (t === 'node' || t === 'leaf' || t === 'parseError') return v
44
- }
45
- return { _tag: 'leaf', value: typeof v === 'string' ? v : (typeof v === 'object' && v !== null ? input.slice(s, e) : ''), span: { start: s, end: e } }
46
- }
47
-
48
- /** Same semantics as `trackLinesInto` in `src/table/assemble.ts`. (Named for the
49
- * source lowering before it, deleted in `37c57b5`.) */
50
- function trackLines(ctx: ParseContext, input: string, end: number): void {
51
- const from = ctx._lineScannedTo ?? 0
52
- if (end <= from) return
53
- const starts = ctx._lineStarts
54
- if (starts === undefined) return
55
- for (let i = from; i < end; i++) if (input.charCodeAt(i) === 10) starts.push(i + 1)
56
- ctx._lineScannedTo = end
57
- }
58
-
59
- /** Same semantics as `lineCol` in `src/table/run-support.ts`, which is where the
60
- * live copy moved. (Named for the source lowering before it, deleted in `37c57b5`.) */
61
- function lineCol(ctx: ParseContext, offset: number): [number, number] {
62
- const starts = ctx._lineStarts ?? [0]
63
- let lo = 0, hi = starts.length - 1
64
- while (lo < hi) {
65
- const mid = (lo + hi + 1) >> 1
66
- if (starts[mid]! <= offset) lo = mid
67
- else hi = mid - 1
68
- }
69
- return [lo + 1, offset - starts[lo]! + 1]
70
- }
71
-
72
- function spanLines(ctx: ParseContext, start: number, end: number): { start: number; end: number; startLine: number; startColumn: number; endLine: number; endColumn: number } {
73
- const s = lineCol(ctx, start), e = lineCol(ctx, end)
74
- return { start, end, startLine: s[0], startColumn: s[1], endLine: e[0], endColumn: e[1] }
75
- }
76
-
77
- function classHas(cls: ResolvedClass, code: number): boolean {
78
- if (code < 0) return false
79
- if (code < 128) return cls.ascii[code] === 1
80
- const hi = cls.hi
81
- for (let i = 0; i < hi.length; i += 2) if (code >= hi[i]! && code <= hi[i + 1]!) return true
82
- return false
83
- }
84
-
85
- type Driver = {
86
- exec: (ip: number, input: string, pos: number, ctx: ParseContext) => unknown
87
- end: () => number
88
- }
89
-
90
- function makeDriver(
91
- code: Int32Array,
92
- k: readonly unknown[],
93
- fns: readonly unknown[],
94
- cc: readonly ResolvedClass[],
95
- fx: readonly (readonly string[])[],
96
- disp: readonly ResolvedDispatch[],
97
- hostCst: boolean,
98
- ): Driver {
99
- /** Shared end-position out-parameter (`_pfEnd` in emitted code). */
100
- let END = 0
101
-
102
- /**
103
- * Capture goes through the runtime's own buffer (`src/cst/capture-buffer.ts`),
104
- * not a hand-rolled copy: it is already the shared mechanism the interpreter
105
- * uses, and a second implementation of it is precisely the duplication G5 is
106
- * about removing.
107
- */
108
- function pushLeaf(ctx: ParseContext, value: string, s: number, e: number): void {
109
- const lf: Leaf = { _tag: 'leaf', value, span: { start: s, end: e } }
110
- pushCstLeaf(ctx, lf)
111
- }
112
-
113
- /**
114
- * Is there anything a failed branch could need to UNRECORD?
115
- *
116
- * `saveCstMark` allocates a 5-field object and `saveTriviaMark` allocates that
117
- * plus a 7-field one; `rollbackTrivia` allocates a third to call through. In
118
- * the driver those sat in the repetition and choice loops, so a parse
119
- * allocated three-to-four objects PER ITEM that codegen never allocates (it
120
- * emits scalar locals). That is a per-item cost, which is what the widening
121
- * gap with input size was: +82% small, +275% large.
122
- *
123
- * When every sink either rollback touches is absent, nothing was recorded,
124
- * so nothing needs unrecording and the mark need not exist. A grammar with no
125
- * `node()` and no trivia log — json, csv — never allocates one.
126
- */
127
- function rollbackNeeded(ctx: ParseContext): boolean {
128
- return ctx._cstBuf !== undefined
129
- || ctx._cstLeaves !== undefined
130
- || ctx._cstRawChildren !== undefined
131
- || ctx._cstTriviaLog !== undefined
132
- || ctx._fields !== undefined
133
- || ctx._errors !== undefined
134
- || ctx._triviaLog !== undefined
135
- || ctx._rootTriviaLog !== undefined
136
- }
137
-
138
- /**
139
- * The lead character at `pos`, as the first-set tables index it.
140
- *
141
- * `charCodeAt` is the hot read; only a high surrogate needs the code-point
142
- * decode, so that branch is paid on astral input and nowhere else.
143
- */
144
- function skipTrivia(input: string, cur: number, ctx: ParseContext): number {
145
- if (needsDeferredTriviaCommit(ctx)) {
146
- return commitTriviaScan(scanTriviaCompact(input, cur, ctx))
147
- }
148
- return advanceTrivia(input, cur, ctx)
149
- }
150
-
151
- function lead(input: string, pos: number): number {
152
- if (pos >= input.length) return -1
153
- const c = input.charCodeAt(pos)
154
- if (c < 0xd800 || c > 0xdbff) return c
155
- return input.codePointAt(pos) ?? c
156
- }
157
-
158
- function exec(ip: number, input: string, pos: number, ctx: ParseContext): unknown {
159
- switch (code[ip]) {
160
- case OP_LIT: {
161
- const s = k[code[ip + 1]!] as string
162
- if (input.startsWith(s, pos)) {
163
- const e = pos + s.length
164
- if (cstCaptureActive(ctx)) pushLeaf(ctx, s, pos, e)
165
- END = e
166
- return s
167
- }
168
- ctx._fe = pos; ctx._fx = fx[code[ip + 2]!] as string[]
169
- return FAIL
170
- }
171
- case OP_RX: {
172
- const re = k[code[ip + 1]!] as RegExp
173
- re.lastIndex = pos
174
- const m = re.exec(input)
175
- if (m !== null) {
176
- const v = m[0]
177
- const e = pos + v.length
178
- if (cstCaptureActive(ctx)) pushLeaf(ctx, v, pos, e)
179
- END = e
180
- return v
181
- }
182
- ctx._fe = pos; ctx._fx = fx[code[ip + 2]!] as string[]
183
- return FAIL
184
- }
185
- case OP_LIT_TRACK: {
186
- const s = k[code[ip + 1]!] as string
187
- if (input.startsWith(s, pos)) {
188
- const e = pos + s.length
189
- if (cstCaptureActive(ctx)) pushLeaf(ctx, s, pos, e)
190
- trackLines(ctx, input, e)
191
- END = e
192
- return s
193
- }
194
- ctx._fe = pos; ctx._fx = fx[code[ip + 2]!] as string[]
195
- return FAIL
196
- }
197
- case OP_RX_TRACK: {
198
- const re = k[code[ip + 1]!] as RegExp
199
- re.lastIndex = pos
200
- const m = re.exec(input)
201
- if (m !== null) {
202
- const v = m[0]
203
- const e = pos + v.length
204
- if (cstCaptureActive(ctx)) pushLeaf(ctx, v, pos, e)
205
- trackLines(ctx, input, e)
206
- END = e
207
- return v
208
- }
209
- ctx._fe = pos; ctx._fx = fx[code[ip + 2]!] as string[]
210
- return FAIL
211
- }
212
- case OP_EMPTY:
213
- END = pos
214
- return ''
215
-
216
- case OP_GATE: {
217
- if (!classHas(cc[code[ip + 1]!]!, lead(input, pos))) {
218
- ctx._fe = pos; ctx._fx = fx[code[ip + 3]!] as string[]
219
- return FAIL
220
- }
221
- return exec(code[ip + 2]!, input, pos, ctx)
222
- }
223
- case OP_RULE:
224
- return exec(code[ip + 1]!, input, pos, ctx)
225
-
226
- case OP_EXPECT: {
227
- const v = exec(code[ip + 1]!, input, pos, ctx)
228
- if (v !== FAIL) return v
229
- // Mirrors src/combinators/expect.ts:135-150 — succeed at zero width with
230
- // a ParseError value, and record it in the flat sink when present.
231
- const span = { start: pos, end: pos }
232
- const err = { _tag: 'parseError' as const, span, expected: fx[code[ip + 2]!] as string[] }
233
- ctx._errors?.push(err)
234
- END = pos
235
- return err
236
- }
237
-
238
- case OP_SCOPE: {
239
- const ki = code[ip + 1]!
240
- const saved = ctx.trivia
241
- ctx.trivia = ki < 0 ? undefined : (k[ki] as ParseContext['trivia'])
242
- const v = exec(code[ip + 2]!, input, pos, ctx)
243
- ctx.trivia = saved
244
- return v
245
- }
246
-
247
- case OP_SEQ:
248
- case OP_SEQV: {
249
- const n = code[ip + 1]!
250
- const values: unknown[] | undefined = code[ip] === OP_SEQ ? [] : undefined
251
- let cur = pos
252
- for (let i = 0; i < n; i++) {
253
- const child = code[ip + 2 + i]!
254
- if (i > 0 && ctx.trivia !== undefined) {
255
- const mark = rollbackNeeded(ctx) ? saveTriviaMark(ctx) : null
256
- let scanEnd: number
257
- if (needsDeferredTriviaCommit(ctx)) {
258
- scanEnd = commitTriviaScan(scanTriviaCompact(input, cur, ctx))
259
- } else {
260
- scanEnd = advanceTrivia(input, cur, ctx)
261
- }
262
- const v = exec(child, input, scanEnd, ctx)
263
- if (v === FAIL) return FAIL
264
- if (END > scanEnd) cur = END
265
- else if (mark !== null) rollbackTrivia(ctx, mark)
266
- if (values !== undefined) values.push(v)
267
- continue
268
- }
269
- // TERMINAL FAST PATH. Terminals are the majority of executed
270
- // instructions, and reaching one through `exec` costs a JS call frame
271
- // plus a switch dispatch that the emitted code does not pay. Running
272
- // LIT/RX in place removes both. The duplication is in the DRIVER,
273
- // which ships once for every grammar — the cost this design trades on.
274
- const cop = code[child]
275
- if (cop === OP_LIT) {
276
- const lit = k[code[child + 1]!] as string
277
- if (!input.startsWith(lit, cur)) {
278
- ctx._fe = cur; ctx._fx = fx[code[child + 2]!] as string[]
279
- return FAIL
280
- }
281
- const e = cur + lit.length
282
- if (cstCaptureActive(ctx)) pushLeaf(ctx, lit, cur, e)
283
- if (values !== undefined) values.push(lit)
284
- cur = e
285
- continue
286
- }
287
- if (cop === OP_RX) {
288
- const re = k[code[child + 1]!] as RegExp
289
- re.lastIndex = cur
290
- const m = re.exec(input)
291
- if (m === null) {
292
- ctx._fe = cur; ctx._fx = fx[code[child + 2]!] as string[]
293
- return FAIL
294
- }
295
- const mv = m[0]
296
- const e = cur + mv.length
297
- if (cstCaptureActive(ctx)) pushLeaf(ctx, mv, cur, e)
298
- if (values !== undefined) values.push(mv)
299
- cur = e
300
- continue
301
- }
302
- const v = exec(child, input, cur, ctx)
303
- if (v === FAIL) return FAIL
304
- if (values !== undefined) values.push(v)
305
- cur = END
306
- }
307
- END = cur
308
- return values
309
- }
310
-
311
- case OP_CHOICE: {
312
- const d = code[ip + 1]!
313
- const base = ip + 3
314
- if (d >= 0) {
315
- const table = disp[d]!
316
- const c = lead(input, pos)
317
- let arm = -1
318
- if (c >= 0 && c < 128) {
319
- const a = table.ascii[c]!
320
- if (a !== 0) arm = a - 1
321
- } else if (c >= 128) {
322
- const hi = table.hi
323
- for (let i = 0; i < hi.length; i += 3) {
324
- if (c >= hi[i]! && c <= hi[i + 1]!) { arm = hi[i + 2]!; break }
325
- }
326
- }
327
- if (arm >= 0) {
328
- const v = exec(code[base + arm]!, input, pos, ctx)
329
- if (v !== FAIL) return v
330
- }
331
- const open = table.open
332
- if (open.length === 0) return FAIL
333
- const mark: CstRollbackMark | null = rollbackNeeded(ctx) ? saveCstMark(ctx) : null
334
- if (mark !== null) rollbackCstCapture(ctx, mark)
335
- for (let i = 0; i < open.length; i++) {
336
- const v = exec(code[base + open[i]!]!, input, pos, ctx)
337
- if (v !== FAIL) return v
338
- if (mark !== null) rollbackCstCapture(ctx, mark)
339
- }
340
- return FAIL
341
- }
342
- const n = code[ip + 2]!
343
- const mark: CstRollbackMark | null = rollbackNeeded(ctx) ? saveCstMark(ctx) : null
344
- for (let i = 0; i < n; i++) {
345
- const v = exec(code[base + i]!, input, pos, ctx)
346
- if (v !== FAIL) return v
347
- if (mark !== null) rollbackCstCapture(ctx, mark)
348
- }
349
- return FAIL
350
- }
351
-
352
- case OP_OPT: {
353
- const mark: CstRollbackMark | null = rollbackNeeded(ctx) ? saveCstMark(ctx) : null
354
- const v = exec(code[ip + 1]!, input, pos, ctx)
355
- if (v === FAIL) {
356
- if (mark !== null) rollbackCstCapture(ctx, mark)
357
- END = pos
358
- // NULL, not undefined. `optional()` yields `null` on no-match
359
- // (src/combinators/repeat.ts:269,277) and grammars TEST for it:
360
- // examples/lang's `call` reducer is `if (args === null) return callee`,
361
- // so `undefined` there turned a bare identifier into a call node with
362
- // `args: undefined`. The parse succeeded and only the tree moved.
363
- return null
364
- }
365
- return v
366
- }
367
-
368
- case OP_REP:
369
- case OP_REPV: {
370
- const child = code[ip + 1]!
371
- const min = code[ip + 2]!
372
- const max = code[ip + 3]!
373
- const sep = code[ip + 4]!
374
- const out: unknown[] | undefined = code[ip] === OP_REP ? [] : undefined
375
- const hasTrivia = ctx.trivia !== undefined
376
- const needMark = rollbackNeeded(ctx)
377
- let cur = pos
378
- let count = 0
379
- for (;;) {
380
- if (max >= 0 && count >= max) break
381
- // One mark pair for the whole loop when a rollback is even possible,
382
- // refreshed per iteration rather than reallocated.
383
- const cmark = needMark ? saveCstMark(ctx) : null
384
- const tmark = needMark ? saveTriviaMark(ctx) : null
385
- let itemStart = cur
386
- if (sep >= 0 && count > 0) {
387
- // separator, with trivia on BOTH sides — mirrors repeat.ts's sepBy loop
388
- let sp = cur
389
- if (hasTrivia) sp = skipTrivia(input, sp, ctx)
390
- const sv = exec(sep, input, sp, ctx)
391
- if (sv === FAIL) {
392
- if (tmark !== null) rollbackTrivia(ctx, tmark)
393
- if (cmark !== null) rollbackCstCapture(ctx, cmark)
394
- break
395
- }
396
- itemStart = hasTrivia ? skipTrivia(input, END, ctx) : END
397
- } else if (hasTrivia) {
398
- // Trivia precedes EVERY item, the first included: `repItem` in
399
- // repeat.ts does this, and skipping it only for later items dropped
400
- // exactly one trivia-log entry per repetition — invisible in the
401
- // parse, visible in a node's `triviaLog`.
402
- itemStart = skipTrivia(input, itemStart, ctx)
403
- }
404
- // Nothing but trivia left: don't speculatively parse an item at EOF.
405
- if (itemStart >= input.length) {
406
- if (tmark !== null) rollbackTrivia(ctx, tmark)
407
- if (cmark !== null) rollbackCstCapture(ctx, cmark)
408
- break
409
- }
410
- const v = exec(child, input, itemStart, ctx)
411
- if (v === FAIL) {
412
- if (tmark !== null) rollbackTrivia(ctx, tmark)
413
- if (cmark !== null) rollbackCstCapture(ctx, cmark)
414
- break
415
- }
416
- if (END === itemStart) {
417
- // Zero-width item: it cannot make progress, so stop without taking it.
418
- if (tmark !== null) rollbackTrivia(ctx, tmark)
419
- if (cmark !== null) rollbackCstCapture(ctx, cmark)
420
- break
421
- }
422
- if (out !== undefined) out.push(v)
423
- cur = END
424
- count++
425
- }
426
- if (count < min) return FAIL
427
- END = cur
428
- return out
429
- }
430
-
431
- case OP_XFORM: {
432
- const v = exec(code[ip + 2]!, input, pos, ctx)
433
- if (v === FAIL) return FAIL
434
- const fn = fns[code[ip + 1]!] as (value: unknown, span: { start: number; end: number }) => unknown
435
- return fn(v, { start: pos, end: END })
436
- }
437
-
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
- }
460
- if (v === FAIL) return FAIL
461
- const end = END
462
- const fn = fns[code[ip + 1]!] as (value: unknown, span: { start: number; end: number }) => unknown
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
- })
469
- END = end
470
- return out
471
- }
472
-
473
- case OP_NODE:
474
- case OP_NODE_TRACK: {
475
- // `flags` bit 2 = the builder reads `triviaLog`. Decided by
476
- // `src/table/encode.ts` from the reducer's arity — the SAME analysis
477
- // codegen runs — and baked into the row, so the driver reads a bit
478
- // rather than re-deriving anything.
479
- const flags = code[ip + 3]!
480
- const saved = beginCstNodeCapture(ctx)
481
- if ((flags & 4) === 0) ctx.captureTrivia = false
482
- const v = exec(code[ip + 2]!, input, pos, ctx)
483
- const cap = endCstNodeCapture(ctx, saved)
484
- if (v === FAIL) return FAIL
485
- const end = END
486
- const build = fns[code[ip + 1]!] as (
487
- children: readonly unknown[], fields: undefined, span: { start: number; end: number },
488
- rawChildren: readonly unknown[], triviaLog: readonly number[], state: unknown,
489
- ) => unknown
490
- const st = (flags & 8) !== 0 ? ctx.state : undefined
491
- const nd = build(
492
- cap.children, undefined,
493
- code[ip] === OP_NODE_TRACK ? spanLines(ctx, pos, end) : { start: pos, end },
494
- cap.rawChildren, (flags & 4) !== 0 ? cap.triviaLog : EMPTY_TL, st,
495
- )
496
- pushCstChild(ctx, nd, rawEntry(nd, input, pos, end))
497
- END = end
498
- return nd
499
- }
500
-
501
- case OP_NOT: {
502
- const mark: CstRollbackMark | null = rollbackNeeded(ctx) ? saveCstMark(ctx) : null
503
- const v = exec(code[ip + 1]!, input, pos, ctx)
504
- if (mark !== null) rollbackCstCapture(ctx, mark)
505
- if (v === FAIL) { END = pos; return null }
506
- ctx._fe = pos
507
- return FAIL
508
- }
509
-
510
- case OP_PEEK: {
511
- const mark: CstRollbackMark | null = rollbackNeeded(ctx) ? saveCstMark(ctx) : null
512
- const v = exec(code[ip + 1]!, input, pos, ctx)
513
- if (mark !== null) rollbackCstCapture(ctx, mark)
514
- if (v === FAIL) return FAIL
515
- END = pos
516
- return null
517
- }
518
-
519
- default:
520
- throw new Error(`table driver: unknown opcode ${String(code[ip])} at ${ip}`)
521
- }
522
- }
523
-
524
- return { exec, end: () => END }
525
- }
526
-
527
- /**
528
- * Turn a program into the rule map a compiled artifact exports.
529
- *
530
- * The entries have the SAME signature as codegen rule functions, so `run()`,
531
- * the linker's public wrappers and every consumer are unchanged.
532
- */
533
- export function execRulesBaseline(source: TableProgram | CompactProgram): Record<string, TableRule> {
534
- const prog = expandCompact(source)
535
- const t = resolveTable(prog)
536
- const d = makeDriver(t.code, t.k, t.fns, t.cc, t.fx, t.disp, prog.hostMode === 'cst')
537
- const out: Record<string, TableRule> = {}
538
- // Chosen ONCE, from table data, at rule-map construction. Not a per-parse
539
- // branch on an option: a plain table never has this wrapper at all.
540
- const lines = prog.lines === 1
541
- for (const name of Object.keys(prog.rules)) {
542
- const entry = prog.rules[name]!
543
- out[name] = (input: string, pos: number, ctx: ParseContext): ParseResult<unknown> => {
544
- ctx._fe = -1
545
- ctx._fx = EMPTY_FX
546
- if (lines && ctx._lineStarts === undefined) { ctx._lineStarts = [0]; ctx._lineScannedTo = 0 }
547
- const v = d.exec(entry, input, pos, ctx)
548
- if (v === FAIL) {
549
- const fe = ctx._fe
550
- const at = fe === undefined || fe < 0 ? pos : fe
551
- return { ok: false, expected: (ctx._fx ?? EMPTY_FX) as string[], span: { start: at, end: at } }
552
- }
553
- return { ok: true, value: v, span: { start: pos, end: d.end() } }
554
- }
555
- }
556
- return out
557
- }