@thomasfosterau/effect-parse 0.4.1 → 0.6.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/CHANGELOG.md +202 -0
- package/README.md +56 -14
- package/dist/Ascii.d.ts +11 -4
- package/dist/Ascii.d.ts.map +1 -1
- package/dist/Ascii.js +12 -4
- package/dist/Ascii.js.map +1 -1
- package/dist/Binary.d.ts +5 -2
- package/dist/Binary.d.ts.map +1 -1
- package/dist/Binary.js +83 -37
- package/dist/Binary.js.map +1 -1
- package/dist/Bits.d.ts.map +1 -1
- package/dist/Bits.js +11 -4
- package/dist/Bits.js.map +1 -1
- package/dist/Compiler.d.ts +74 -2
- package/dist/Compiler.d.ts.map +1 -1
- package/dist/Compiler.js +78 -1
- package/dist/Compiler.js.map +1 -1
- package/dist/Expression.d.ts.map +1 -1
- package/dist/Expression.js +86 -74
- package/dist/Expression.js.map +1 -1
- package/dist/Input.d.ts +56 -5
- package/dist/Input.d.ts.map +1 -1
- package/dist/Input.js +70 -1
- package/dist/Input.js.map +1 -1
- package/dist/Nom.d.ts +5 -15
- package/dist/Nom.d.ts.map +1 -1
- package/dist/Nom.js.map +1 -1
- package/dist/ParseError.d.ts +69 -11
- package/dist/ParseError.d.ts.map +1 -1
- package/dist/ParseError.js +129 -28
- package/dist/ParseError.js.map +1 -1
- package/dist/Parser.d.ts +375 -29
- package/dist/Parser.d.ts.map +1 -1
- package/dist/Parser.js +394 -37
- package/dist/Parser.js.map +1 -1
- package/dist/ParserSchema.d.ts.map +1 -1
- package/dist/ParserSchema.js +2 -3
- package/dist/ParserSchema.js.map +1 -1
- package/dist/ParserStream.d.ts +122 -14
- package/dist/ParserStream.d.ts.map +1 -1
- package/dist/ParserStream.js +293 -62
- package/dist/ParserStream.js.map +1 -1
- package/dist/Syntax.d.ts +23 -5
- package/dist/Syntax.d.ts.map +1 -1
- package/dist/Syntax.js +1 -1
- package/dist/Syntax.js.map +1 -1
- package/dist/Text.d.ts.map +1 -1
- package/dist/Text.js +21 -15
- package/dist/Text.js.map +1 -1
- package/dist/Token.d.ts +65 -11
- package/dist/Token.d.ts.map +1 -1
- package/dist/Token.js +73 -26
- package/dist/Token.js.map +1 -1
- package/dist/internal/compiler/closure.d.ts +1 -1
- package/dist/internal/compiler/closure.d.ts.map +1 -1
- package/dist/internal/compiler/closure.js +263 -113
- package/dist/internal/compiler/closure.js.map +1 -1
- package/dist/internal/compiler/entries.d.ts +3 -1
- package/dist/internal/compiler/entries.d.ts.map +1 -1
- package/dist/internal/compiler/entries.js +3 -2
- package/dist/internal/compiler/entries.js.map +1 -1
- package/dist/internal/compiler/first.d.ts.map +1 -1
- package/dist/internal/compiler/first.js +9 -2
- package/dist/internal/compiler/first.js.map +1 -1
- package/dist/internal/compiler/graph.d.ts +39 -0
- package/dist/internal/compiler/graph.d.ts.map +1 -0
- package/dist/internal/compiler/graph.js +78 -0
- package/dist/internal/compiler/graph.js.map +1 -0
- package/dist/internal/compiler/jit.d.ts +5 -0
- package/dist/internal/compiler/jit.d.ts.map +1 -1
- package/dist/internal/compiler/jit.js +208 -106
- package/dist/internal/compiler/jit.js.map +1 -1
- package/dist/internal/compiler/registry.d.ts +11 -3
- package/dist/internal/compiler/registry.d.ts.map +1 -1
- package/dist/internal/compiler/registry.js.map +1 -1
- package/dist/internal/compiler/search.d.ts +32 -0
- package/dist/internal/compiler/search.d.ts.map +1 -0
- package/dist/internal/compiler/search.js +68 -0
- package/dist/internal/compiler/search.js.map +1 -0
- package/dist/internal/compiler/sets.d.ts.map +1 -1
- package/dist/internal/compiler/sets.js +1 -1
- package/dist/internal/compiler/sets.js.map +1 -1
- package/dist/internal/compiler/wasm.d.ts +8 -0
- package/dist/internal/compiler/wasm.d.ts.map +1 -0
- package/dist/internal/compiler/wasm.js +2840 -0
- package/dist/internal/compiler/wasm.js.map +1 -0
- package/dist/internal/compiler/wasmAsm.d.ts +199 -0
- package/dist/internal/compiler/wasmAsm.d.ts.map +1 -0
- package/dist/internal/compiler/wasmAsm.js +427 -0
- package/dist/internal/compiler/wasmAsm.js.map +1 -0
- package/dist/internal/entries.d.ts +3 -1
- package/dist/internal/entries.d.ts.map +1 -1
- package/dist/internal/entries.js.map +1 -1
- package/dist/internal/node.d.ts +77 -3
- package/dist/internal/node.d.ts.map +1 -1
- package/dist/internal/node.js +54 -10
- package/dist/internal/node.js.map +1 -1
- package/dist/internal/numbers.d.ts.map +1 -1
- package/dist/internal/numbers.js.map +1 -1
- package/dist/internal/run.d.ts +14 -0
- package/dist/internal/run.d.ts.map +1 -1
- package/dist/internal/run.js +53 -11
- package/dist/internal/run.js.map +1 -1
- package/dist/internal/stack.d.ts +14 -0
- package/dist/internal/stack.d.ts.map +1 -1
- package/dist/internal/stack.js +24 -2
- package/dist/internal/stack.js.map +1 -1
- package/dist/internal/syntax.d.ts +4 -4
- package/dist/internal/syntax.d.ts.map +1 -1
- package/dist/internal/syntax.js +18 -10
- package/dist/internal/syntax.js.map +1 -1
- package/dist/internal/text.d.ts +32 -0
- package/dist/internal/text.d.ts.map +1 -1
- package/dist/internal/text.js +68 -14
- package/dist/internal/text.js.map +1 -1
- package/dist/internal/tokens.d.ts +20 -0
- package/dist/internal/tokens.d.ts.map +1 -1
- package/dist/internal/tokens.js +72 -39
- package/dist/internal/tokens.js.map +1 -1
- package/dist/internal/tuple.d.ts +6 -0
- package/dist/internal/tuple.d.ts.map +1 -0
- package/dist/internal/tuple.js +170 -0
- package/dist/internal/tuple.js.map +1 -0
- package/package.json +18 -10
- package/src/Ascii.ts +47 -12
- package/src/Binary.ts +174 -73
- package/src/Bits.ts +16 -6
- package/src/Compiler.ts +101 -10
- package/src/Expression.ts +94 -81
- package/src/Input.ts +99 -7
- package/src/Nom.ts +15 -5
- package/src/ParseError.ts +209 -52
- package/src/Parser.ts +684 -143
- package/src/ParserSchema.ts +1 -2
- package/src/ParserStream.ts +361 -68
- package/src/Syntax.ts +60 -36
- package/src/Text.ts +32 -27
- package/src/Token.ts +152 -34
- package/src/internal/compiler/closure.ts +371 -153
- package/src/internal/compiler/entries.ts +4 -2
- package/src/internal/compiler/first.ts +16 -0
- package/src/internal/compiler/graph.ts +96 -0
- package/src/internal/compiler/jit.ts +319 -169
- package/src/internal/compiler/registry.ts +10 -2
- package/src/internal/compiler/search.ts +68 -0
- package/src/internal/compiler/sets.ts +7 -3
- package/src/internal/compiler/wasm.ts +3276 -0
- package/src/internal/compiler/wasmAsm.ts +484 -0
- package/src/internal/entries.ts +8 -2
- package/src/internal/node.ts +103 -7
- package/src/internal/numbers.ts +3 -1
- package/src/internal/run.ts +73 -12
- package/src/internal/stack.ts +25 -1
- package/src/internal/syntax.ts +39 -18
- package/src/internal/text.ts +97 -9
- package/src/internal/tokens.ts +106 -14
- package/src/internal/tuple.ts +141 -0
package/src/internal/tokens.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
// fallow-ignore-file duplicate-export -- `End` and `scan` here are the token-set versions of the text
|
|
2
|
-
// helpers in ./text.ts; both are internal modules, imported by path, never re-exported from one barrel.
|
|
3
1
|
import * as Predicate from "effect/Predicate"
|
|
4
2
|
import * as Result from "effect/Result"
|
|
5
3
|
import type { Input, State } from "../Input.js"
|
|
@@ -22,7 +20,7 @@ export interface TokenRangeLike {
|
|
|
22
20
|
readonly to: string | number
|
|
23
21
|
}
|
|
24
22
|
|
|
25
|
-
const isRange = (u: unknown): u is TokenRangeLike => Predicate.isObjectOrArray(u) && (u
|
|
23
|
+
const isRange = (u: unknown): u is TokenRangeLike => Predicate.isObjectOrArray(u) && Predicate.isTagged(u, "TokenRange")
|
|
26
24
|
|
|
27
25
|
const toCode = (u: string | number): number => (Predicate.isNumber(u) ? u : firstCodePoint(u))
|
|
28
26
|
|
|
@@ -67,7 +65,10 @@ const compileCode = (u: unknown): ((code: number) => boolean) => {
|
|
|
67
65
|
return bitmap(codes)
|
|
68
66
|
}
|
|
69
67
|
if (Predicate.isNumber(u)) return (x) => x === u
|
|
70
|
-
if (Predicate.isFunction(u))
|
|
68
|
+
if (Predicate.isFunction(u)) {
|
|
69
|
+
// SAFETY: a predicate compiled for codes is a byte, code point or `codePoints` predicate (`compile` wraps string predicates first)
|
|
70
|
+
return u as (code: number) => boolean
|
|
71
|
+
}
|
|
71
72
|
if (u instanceof Uint8Array) return bitmap(Array.from(u))
|
|
72
73
|
if (isRange(u)) {
|
|
73
74
|
const from = toCode(u.from)
|
|
@@ -98,7 +99,10 @@ const bitmap = (codes: ReadonlyArray<number>): ((code: number) => boolean) => {
|
|
|
98
99
|
}
|
|
99
100
|
|
|
100
101
|
const compileToken = (u: unknown): ((token: unknown) => boolean) => {
|
|
101
|
-
if (Predicate.isFunction(u))
|
|
102
|
+
if (Predicate.isFunction(u)) {
|
|
103
|
+
// SAFETY: a predicate in an array input's token set takes the array's elements, the tokens tested here
|
|
104
|
+
return u as (token: unknown) => boolean
|
|
105
|
+
}
|
|
102
106
|
if (Array.isArray(u)) {
|
|
103
107
|
const set = new Set(u)
|
|
104
108
|
return (t) => set.has(t)
|
|
@@ -130,8 +134,13 @@ export const codePointPredicate = <F extends (c: never) => boolean>(
|
|
|
130
134
|
ranges?: ReadonlyArray<readonly [number, number]>
|
|
131
135
|
): F => {
|
|
132
136
|
Object.defineProperty(f, "name", { value: name })
|
|
133
|
-
|
|
134
|
-
|
|
137
|
+
// SAFETY: `f` is a function object, which takes new properties; the intersection only names the two markers set here
|
|
138
|
+
const marked = f as F & {
|
|
139
|
+
[CodePointPredicate]?: true
|
|
140
|
+
[PredicateRanges]?: ReadonlyArray<readonly [number, number]>
|
|
141
|
+
}
|
|
142
|
+
marked[CodePointPredicate] = true
|
|
143
|
+
if (ranges !== undefined) marked[PredicateRanges] = ranges
|
|
135
144
|
return f
|
|
136
145
|
}
|
|
137
146
|
|
|
@@ -144,6 +153,7 @@ export const compile = (set: unknown, kind: "string" | "code" = "code"): Matcher
|
|
|
144
153
|
const description = describeSet(set)
|
|
145
154
|
let code: (code: number) => boolean
|
|
146
155
|
if (Predicate.isFunction(set) && kind === "string" && !(CodePointPredicate in set)) {
|
|
156
|
+
// SAFETY: an unmarked predicate in a string token set takes each character as a string (`StringTokenSet`)
|
|
147
157
|
const pred = set as (c: string) => boolean
|
|
148
158
|
code = (x) => pred(charOf(x))
|
|
149
159
|
} else {
|
|
@@ -227,6 +237,7 @@ const compileAnyImpl = (set: unknown): AnyMatcher => {
|
|
|
227
237
|
*/
|
|
228
238
|
export const splitSurrogate = (state: State<Input>, i: number): boolean => {
|
|
229
239
|
if (!state.partial || i !== state.end - 1) return false
|
|
240
|
+
// SAFETY: called only on string input (the `inputKind === 0` paths of `nextIf`, `scan`, `advance` and `Token.any`)
|
|
230
241
|
const c = (state.input as string).charCodeAt(i)
|
|
231
242
|
return c >= 0xd800 && c <= 0xdbff
|
|
232
243
|
}
|
|
@@ -253,6 +264,7 @@ export const nextIf = (state: State<Input>, m: AnyMatcher, negate: boolean): unk
|
|
|
253
264
|
if (pos >= state.end) return End
|
|
254
265
|
switch (state.inputKind) {
|
|
255
266
|
case 0: {
|
|
267
|
+
// SAFETY: `inputKind` 0 is set only for a string input
|
|
256
268
|
const s = state.input as string
|
|
257
269
|
if (splitSurrogate(state, pos)) return End
|
|
258
270
|
const cp = codePointAt(s, pos)
|
|
@@ -262,12 +274,14 @@ export const nextIf = (state: State<Input>, m: AnyMatcher, negate: boolean): unk
|
|
|
262
274
|
return len === 1 ? s[pos] : s.slice(pos, pos + 2)
|
|
263
275
|
}
|
|
264
276
|
case 1: {
|
|
277
|
+
// SAFETY: `inputKind` 1 is set only for a `Uint8Array` input
|
|
265
278
|
const b = (state.input as Uint8Array)[pos]
|
|
266
279
|
if (m.forCode.code(b) === negate) return NoMatch
|
|
267
280
|
state.pos = pos + 1
|
|
268
281
|
return b
|
|
269
282
|
}
|
|
270
283
|
default: {
|
|
284
|
+
// SAFETY: `inputKind` 2 is set for every input that is neither a string nor a `Uint8Array`, an array
|
|
271
285
|
const t = (state.input as ReadonlyArray<unknown>)[pos]
|
|
272
286
|
if (m.forCode.token(t) === negate) return NoMatch
|
|
273
287
|
state.pos = pos + 1
|
|
@@ -282,10 +296,32 @@ export interface Scanned extends Advanced {
|
|
|
282
296
|
readonly hitEnd: boolean
|
|
283
297
|
}
|
|
284
298
|
|
|
299
|
+
/** The one record {@link scan} fills; see there. */
|
|
300
|
+
class ScanResult implements Scanned {
|
|
301
|
+
offset = 0
|
|
302
|
+
count = 0
|
|
303
|
+
hitEnd = false
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const scanned = /*#__PURE__*/ new ScanResult()
|
|
307
|
+
|
|
308
|
+
/** Fills {@link scanned} and returns it. */
|
|
309
|
+
const scannedAt = (offset: number, count: number, hitEnd: boolean): Scanned => {
|
|
310
|
+
scanned.offset = offset
|
|
311
|
+
scanned.count = count
|
|
312
|
+
scanned.hitEnd = hitEnd
|
|
313
|
+
return scanned
|
|
314
|
+
}
|
|
315
|
+
|
|
285
316
|
/**
|
|
286
317
|
* Scans forward while tokens satisfy the matcher (xor `negate`), up to `max`
|
|
287
318
|
* tokens. Returns the offset reached, the number of tokens matched, and
|
|
288
319
|
* whether the end of input was hit before `max` tokens.
|
|
320
|
+
*
|
|
321
|
+
* The result is one shared record, overwritten by the next scan: read it
|
|
322
|
+
* before running anything else. Allocating one per call cost the
|
|
323
|
+
* interpreter a record per token, since `scan` (shared by every `takeWhile`)
|
|
324
|
+
* is too polymorphic for V8 to inline and remove it.
|
|
289
325
|
*/
|
|
290
326
|
export const scan = (state: State<Input>, m: AnyMatcher, negate: boolean, max: number): Scanned => {
|
|
291
327
|
let i = state.pos
|
|
@@ -293,45 +329,48 @@ export const scan = (state: State<Input>, m: AnyMatcher, negate: boolean, max: n
|
|
|
293
329
|
let count = 0
|
|
294
330
|
switch (state.inputKind) {
|
|
295
331
|
case 0: {
|
|
332
|
+
// SAFETY: `inputKind` 0 is set only for a string input
|
|
296
333
|
const s = state.input as string
|
|
297
334
|
const test = m.forString.code
|
|
298
335
|
while (count < max) {
|
|
299
|
-
if (i >= end) return
|
|
336
|
+
if (i >= end) return scannedAt(i, count, true)
|
|
300
337
|
const c = s.charCodeAt(i)
|
|
301
338
|
if (c < 0xd800 || c > 0xdbff) {
|
|
302
339
|
if (test(c) === negate) break
|
|
303
340
|
i++
|
|
304
341
|
} else {
|
|
305
|
-
if (splitSurrogate(state, i)) return
|
|
342
|
+
if (splitSurrogate(state, i)) return scannedAt(i, count, true)
|
|
306
343
|
const cp = codePointAt(s, i)
|
|
307
344
|
if (test(cp) === negate) break
|
|
308
345
|
i += cp > 0xffff ? 2 : 1
|
|
309
346
|
}
|
|
310
347
|
count++
|
|
311
348
|
}
|
|
312
|
-
return
|
|
349
|
+
return scannedAt(i, count, false)
|
|
313
350
|
}
|
|
314
351
|
case 1: {
|
|
352
|
+
// SAFETY: `inputKind` 1 is set only for a `Uint8Array` input
|
|
315
353
|
const bytes = state.input as Uint8Array
|
|
316
354
|
const test = m.forCode.code
|
|
317
355
|
while (count < max) {
|
|
318
|
-
if (i >= end) return
|
|
356
|
+
if (i >= end) return scannedAt(i, count, true)
|
|
319
357
|
if (test(bytes[i]) === negate) break
|
|
320
358
|
i++
|
|
321
359
|
count++
|
|
322
360
|
}
|
|
323
|
-
return
|
|
361
|
+
return scannedAt(i, count, false)
|
|
324
362
|
}
|
|
325
363
|
default: {
|
|
364
|
+
// SAFETY: `inputKind` 2 is set for every input that is neither a string nor a `Uint8Array`, an array
|
|
326
365
|
const arr = state.input as ReadonlyArray<unknown>
|
|
327
366
|
const test = m.forCode.token
|
|
328
367
|
while (count < max) {
|
|
329
|
-
if (i >= end) return
|
|
368
|
+
if (i >= end) return scannedAt(i, count, true)
|
|
330
369
|
if (test(arr[i]) === negate) break
|
|
331
370
|
i++
|
|
332
371
|
count++
|
|
333
372
|
}
|
|
334
|
-
return
|
|
373
|
+
return scannedAt(i, count, false)
|
|
335
374
|
}
|
|
336
375
|
}
|
|
337
376
|
}
|
|
@@ -348,6 +387,7 @@ export const advance = (state: State<Input>, start: number, n: number): Advanced
|
|
|
348
387
|
const offset = Math.min(state.end, start + n)
|
|
349
388
|
return { offset, count: offset - start }
|
|
350
389
|
}
|
|
390
|
+
// SAFETY: the other input kinds returned above, and `inputKind` 0 is set only for a string input
|
|
351
391
|
const s = state.input as string
|
|
352
392
|
let i = start
|
|
353
393
|
let count = 0
|
|
@@ -363,6 +403,7 @@ export const advance = (state: State<Input>, start: number, n: number): Advanced
|
|
|
363
403
|
/** Counts tokens between two offsets. */
|
|
364
404
|
export const countTokens = (state: State<Input>, start: number, end: number): number => {
|
|
365
405
|
if (state.inputKind !== 0) return end - start
|
|
406
|
+
// SAFETY: the other input kinds returned above, and `inputKind` 0 is set only for a string input
|
|
366
407
|
const s = state.input as string
|
|
367
408
|
let count = 0
|
|
368
409
|
for (let i = start; i < end; i++) {
|
|
@@ -373,6 +414,46 @@ export const countTokens = (state: State<Input>, start: number, end: number): nu
|
|
|
373
414
|
return count
|
|
374
415
|
}
|
|
375
416
|
|
|
417
|
+
/**
|
|
418
|
+
* The first index at or after `from` of any of `needles` within
|
|
419
|
+
* `[from, state.end)`, or -1. The state remembers, under `needles`, where
|
|
420
|
+
* each one next occurs (`[from, at, end]` at `3 * i`), so successive
|
|
421
|
+
* searches forwards over one input search each needle once per occurrence
|
|
422
|
+
* rather than once per call (as `nextStop` does for `takeTill`).
|
|
423
|
+
*
|
|
424
|
+
* A remembered answer is used only where it still holds: an occurrence, when
|
|
425
|
+
* the search starts between where it was searched from and where it was
|
|
426
|
+
* found, and it still fits before the end; "not found", when the search
|
|
427
|
+
* starts no earlier and the end is no further than the end that search ran
|
|
428
|
+
* to (a parser may narrow `state.end` for a while, and `reset` widens it
|
|
429
|
+
* again, so a miss within a narrower end says nothing about a wider one).
|
|
430
|
+
*/
|
|
431
|
+
export const indexOfAny = (state: State<Input>, needles: ReadonlyArray<Input>, from: number): number => {
|
|
432
|
+
const searches = (state.searches ??= new Map())
|
|
433
|
+
let memo = searches.get(needles)
|
|
434
|
+
if (memo === undefined) {
|
|
435
|
+
memo = needles.flatMap(() => [-1, -1, -1])
|
|
436
|
+
searches.set(needles, memo)
|
|
437
|
+
}
|
|
438
|
+
let best = -1
|
|
439
|
+
for (let i = 0; i < needles.length; i++) {
|
|
440
|
+
const known = memo[3 * i] ?? -1
|
|
441
|
+
let at = memo[3 * i + 1] ?? -1
|
|
442
|
+
const searchedTo = memo[3 * i + 2] ?? -1
|
|
443
|
+
const needle = needles[i] ?? ""
|
|
444
|
+
const stale =
|
|
445
|
+
known < 0 || from < known || (at >= 0 ? at < from || at > state.end - needle.length : state.end > searchedTo)
|
|
446
|
+
if (stale) {
|
|
447
|
+
at = indexOf(state, needle, from)
|
|
448
|
+
memo[3 * i] = from
|
|
449
|
+
memo[3 * i + 1] = at
|
|
450
|
+
memo[3 * i + 2] = state.end
|
|
451
|
+
}
|
|
452
|
+
if (at >= 0 && (best < 0 || at < best)) best = at
|
|
453
|
+
}
|
|
454
|
+
return best
|
|
455
|
+
}
|
|
456
|
+
|
|
376
457
|
/** Finds `needle` within `[from, state.end)`; returns -1 if absent. */
|
|
377
458
|
export const indexOf = (state: State<Input>, needle: Input, from: number): number => {
|
|
378
459
|
// The whole needle must lie before `state.end`, which may narrow the input
|
|
@@ -380,11 +461,14 @@ export const indexOf = (state: State<Input>, needle: Input, from: number): numbe
|
|
|
380
461
|
if (from > last) return -1
|
|
381
462
|
switch (state.inputKind) {
|
|
382
463
|
case 0: {
|
|
464
|
+
// SAFETY: `inputKind` 0 is set only for a string input, and the callers' types give the needle the input's kind
|
|
383
465
|
const i = (state.input as string).indexOf(needle as string, from)
|
|
384
466
|
return i <= last ? i : -1
|
|
385
467
|
}
|
|
386
468
|
case 1: {
|
|
469
|
+
// SAFETY: `inputKind` 1 is set only for a `Uint8Array` input
|
|
387
470
|
const hay = state.input as Uint8Array
|
|
471
|
+
// SAFETY: the callers' types give the needle the input's kind (`Text` encodes it for byte input)
|
|
388
472
|
const n = needle as Uint8Array
|
|
389
473
|
if (n.byteLength === 0) return from
|
|
390
474
|
const first = n[0]
|
|
@@ -398,7 +482,9 @@ export const indexOf = (state: State<Input>, needle: Input, from: number): numbe
|
|
|
398
482
|
return -1
|
|
399
483
|
}
|
|
400
484
|
default: {
|
|
485
|
+
// SAFETY: `inputKind` 2 is set for every input that is neither a string nor a `Uint8Array`, an array
|
|
401
486
|
const hay = state.input as ReadonlyArray<unknown>
|
|
487
|
+
// SAFETY: the callers' types give the needle the input's kind, an array of the same element type
|
|
402
488
|
const n = needle as ReadonlyArray<unknown>
|
|
403
489
|
outer: for (let i = from; i <= last; i++) {
|
|
404
490
|
for (let j = 0; j < n.length; j++) if (hay[i + j] !== n[j]) continue outer
|
|
@@ -428,7 +514,9 @@ const match = (state: State<Input>, lit: Input, caseless: boolean): number => {
|
|
|
428
514
|
const n = Math.min(len, avail)
|
|
429
515
|
switch (state.inputKind) {
|
|
430
516
|
case 0: {
|
|
517
|
+
// SAFETY: `inputKind` 0 is set only for a string input
|
|
431
518
|
const s = state.input as string
|
|
519
|
+
// SAFETY: the callers' types give the literal the input's kind (`Token.literal` encodes a string literal for byte input)
|
|
432
520
|
const l = lit as string
|
|
433
521
|
if (!caseless) {
|
|
434
522
|
if (avail >= len) return s.startsWith(l, pos) ? len : -1
|
|
@@ -440,7 +528,9 @@ const match = (state: State<Input>, lit: Input, caseless: boolean): number => {
|
|
|
440
528
|
return n === len ? len : -2
|
|
441
529
|
}
|
|
442
530
|
case 1: {
|
|
531
|
+
// SAFETY: `inputKind` 1 is set only for a `Uint8Array` input
|
|
443
532
|
const b = state.input as Uint8Array
|
|
533
|
+
// SAFETY: the callers' types give the literal the input's kind (`Token.literal` and `Text` encode string literals for byte input)
|
|
444
534
|
const l = lit as Uint8Array
|
|
445
535
|
for (let i = 0; i < n; i++) {
|
|
446
536
|
const x = b[pos + i]
|
|
@@ -450,7 +540,9 @@ const match = (state: State<Input>, lit: Input, caseless: boolean): number => {
|
|
|
450
540
|
return n === len ? len : -2
|
|
451
541
|
}
|
|
452
542
|
default: {
|
|
543
|
+
// SAFETY: `inputKind` 2 is set for every input that is neither a string nor a `Uint8Array`, an array
|
|
453
544
|
const a = state.input as ReadonlyArray<unknown>
|
|
545
|
+
// SAFETY: the callers' types give the literal the input's kind, an array of the same element type
|
|
454
546
|
const l = lit as ReadonlyArray<unknown>
|
|
455
547
|
for (let i = 0; i < n; i++) if (a[pos + i] !== l[i]) return -1
|
|
456
548
|
return n === len ? len : -2
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tuple steps: run steps in sequence and collect their outputs in an array.
|
|
3
|
+
*
|
|
4
|
+
* V8 pretenures by allocation site, and only array and object literals have
|
|
5
|
+
* one: an array from `new Array(n)` starts young, so tuples that a parse keeps
|
|
6
|
+
* (a `repeat(seq(...))`, a tree) are copied by a scavenge or two before they
|
|
7
|
+
* are promoted. A literal's site learns that its arrays survive, and V8 then
|
|
8
|
+
* allocates them straight into the old generation. So arities up to
|
|
9
|
+
* {@link maxLiteral} build their tuple with a literal of that arity, each its
|
|
10
|
+
* own site; longer tuples fall back to `new Array(n)` (#86).
|
|
11
|
+
*/
|
|
12
|
+
import { Fail } from "../Input.js"
|
|
13
|
+
import type { State } from "../Input.js"
|
|
14
|
+
|
|
15
|
+
type Step = (state: State<any>) => unknown
|
|
16
|
+
|
|
17
|
+
/** The longest tuple built with a literal. */
|
|
18
|
+
const maxLiteral = 8
|
|
19
|
+
|
|
20
|
+
/** A step that runs `steps` in order and returns their outputs as an array, or {@link Fail}. */
|
|
21
|
+
export const tupleStep = (steps: ReadonlyArray<Step>): ((state: State<any>) => Array<unknown> | Fail) => {
|
|
22
|
+
const n = steps.length
|
|
23
|
+
if (n <= maxLiteral) return literals[n](steps)
|
|
24
|
+
return (state) => {
|
|
25
|
+
const out = new Array<unknown>(n)
|
|
26
|
+
for (let i = 0; i < n; i++) {
|
|
27
|
+
const a = steps[i](state)
|
|
28
|
+
if (a === Fail) return Fail
|
|
29
|
+
out[i] = a
|
|
30
|
+
}
|
|
31
|
+
return out
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const literals: ReadonlyArray<(steps: ReadonlyArray<Step>) => (state: State<any>) => Array<unknown> | Fail> = [
|
|
36
|
+
() => () => [],
|
|
37
|
+
([s0]) =>
|
|
38
|
+
(state) => {
|
|
39
|
+
const a0 = s0(state)
|
|
40
|
+
if (a0 === Fail) return Fail
|
|
41
|
+
return [a0]
|
|
42
|
+
},
|
|
43
|
+
([s0, s1]) =>
|
|
44
|
+
(state) => {
|
|
45
|
+
const a0 = s0(state)
|
|
46
|
+
if (a0 === Fail) return Fail
|
|
47
|
+
const a1 = s1(state)
|
|
48
|
+
if (a1 === Fail) return Fail
|
|
49
|
+
return [a0, a1]
|
|
50
|
+
},
|
|
51
|
+
([s0, s1, s2]) =>
|
|
52
|
+
(state) => {
|
|
53
|
+
const a0 = s0(state)
|
|
54
|
+
if (a0 === Fail) return Fail
|
|
55
|
+
const a1 = s1(state)
|
|
56
|
+
if (a1 === Fail) return Fail
|
|
57
|
+
const a2 = s2(state)
|
|
58
|
+
if (a2 === Fail) return Fail
|
|
59
|
+
return [a0, a1, a2]
|
|
60
|
+
},
|
|
61
|
+
([s0, s1, s2, s3]) =>
|
|
62
|
+
(state) => {
|
|
63
|
+
const a0 = s0(state)
|
|
64
|
+
if (a0 === Fail) return Fail
|
|
65
|
+
const a1 = s1(state)
|
|
66
|
+
if (a1 === Fail) return Fail
|
|
67
|
+
const a2 = s2(state)
|
|
68
|
+
if (a2 === Fail) return Fail
|
|
69
|
+
const a3 = s3(state)
|
|
70
|
+
if (a3 === Fail) return Fail
|
|
71
|
+
return [a0, a1, a2, a3]
|
|
72
|
+
},
|
|
73
|
+
([s0, s1, s2, s3, s4]) =>
|
|
74
|
+
(state) => {
|
|
75
|
+
const a0 = s0(state)
|
|
76
|
+
if (a0 === Fail) return Fail
|
|
77
|
+
const a1 = s1(state)
|
|
78
|
+
if (a1 === Fail) return Fail
|
|
79
|
+
const a2 = s2(state)
|
|
80
|
+
if (a2 === Fail) return Fail
|
|
81
|
+
const a3 = s3(state)
|
|
82
|
+
if (a3 === Fail) return Fail
|
|
83
|
+
const a4 = s4(state)
|
|
84
|
+
if (a4 === Fail) return Fail
|
|
85
|
+
return [a0, a1, a2, a3, a4]
|
|
86
|
+
},
|
|
87
|
+
([s0, s1, s2, s3, s4, s5]) =>
|
|
88
|
+
(state) => {
|
|
89
|
+
const a0 = s0(state)
|
|
90
|
+
if (a0 === Fail) return Fail
|
|
91
|
+
const a1 = s1(state)
|
|
92
|
+
if (a1 === Fail) return Fail
|
|
93
|
+
const a2 = s2(state)
|
|
94
|
+
if (a2 === Fail) return Fail
|
|
95
|
+
const a3 = s3(state)
|
|
96
|
+
if (a3 === Fail) return Fail
|
|
97
|
+
const a4 = s4(state)
|
|
98
|
+
if (a4 === Fail) return Fail
|
|
99
|
+
const a5 = s5(state)
|
|
100
|
+
if (a5 === Fail) return Fail
|
|
101
|
+
return [a0, a1, a2, a3, a4, a5]
|
|
102
|
+
},
|
|
103
|
+
([s0, s1, s2, s3, s4, s5, s6]) =>
|
|
104
|
+
(state) => {
|
|
105
|
+
const a0 = s0(state)
|
|
106
|
+
if (a0 === Fail) return Fail
|
|
107
|
+
const a1 = s1(state)
|
|
108
|
+
if (a1 === Fail) return Fail
|
|
109
|
+
const a2 = s2(state)
|
|
110
|
+
if (a2 === Fail) return Fail
|
|
111
|
+
const a3 = s3(state)
|
|
112
|
+
if (a3 === Fail) return Fail
|
|
113
|
+
const a4 = s4(state)
|
|
114
|
+
if (a4 === Fail) return Fail
|
|
115
|
+
const a5 = s5(state)
|
|
116
|
+
if (a5 === Fail) return Fail
|
|
117
|
+
const a6 = s6(state)
|
|
118
|
+
if (a6 === Fail) return Fail
|
|
119
|
+
return [a0, a1, a2, a3, a4, a5, a6]
|
|
120
|
+
},
|
|
121
|
+
([s0, s1, s2, s3, s4, s5, s6, s7]) =>
|
|
122
|
+
(state) => {
|
|
123
|
+
const a0 = s0(state)
|
|
124
|
+
if (a0 === Fail) return Fail
|
|
125
|
+
const a1 = s1(state)
|
|
126
|
+
if (a1 === Fail) return Fail
|
|
127
|
+
const a2 = s2(state)
|
|
128
|
+
if (a2 === Fail) return Fail
|
|
129
|
+
const a3 = s3(state)
|
|
130
|
+
if (a3 === Fail) return Fail
|
|
131
|
+
const a4 = s4(state)
|
|
132
|
+
if (a4 === Fail) return Fail
|
|
133
|
+
const a5 = s5(state)
|
|
134
|
+
if (a5 === Fail) return Fail
|
|
135
|
+
const a6 = s6(state)
|
|
136
|
+
if (a6 === Fail) return Fail
|
|
137
|
+
const a7 = s7(state)
|
|
138
|
+
if (a7 === Fail) return Fail
|
|
139
|
+
return [a0, a1, a2, a3, a4, a5, a6, a7]
|
|
140
|
+
}
|
|
141
|
+
]
|