@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.
Files changed (157) hide show
  1. package/CHANGELOG.md +202 -0
  2. package/README.md +56 -14
  3. package/dist/Ascii.d.ts +11 -4
  4. package/dist/Ascii.d.ts.map +1 -1
  5. package/dist/Ascii.js +12 -4
  6. package/dist/Ascii.js.map +1 -1
  7. package/dist/Binary.d.ts +5 -2
  8. package/dist/Binary.d.ts.map +1 -1
  9. package/dist/Binary.js +83 -37
  10. package/dist/Binary.js.map +1 -1
  11. package/dist/Bits.d.ts.map +1 -1
  12. package/dist/Bits.js +11 -4
  13. package/dist/Bits.js.map +1 -1
  14. package/dist/Compiler.d.ts +74 -2
  15. package/dist/Compiler.d.ts.map +1 -1
  16. package/dist/Compiler.js +78 -1
  17. package/dist/Compiler.js.map +1 -1
  18. package/dist/Expression.d.ts.map +1 -1
  19. package/dist/Expression.js +86 -74
  20. package/dist/Expression.js.map +1 -1
  21. package/dist/Input.d.ts +56 -5
  22. package/dist/Input.d.ts.map +1 -1
  23. package/dist/Input.js +70 -1
  24. package/dist/Input.js.map +1 -1
  25. package/dist/Nom.d.ts +5 -15
  26. package/dist/Nom.d.ts.map +1 -1
  27. package/dist/Nom.js.map +1 -1
  28. package/dist/ParseError.d.ts +69 -11
  29. package/dist/ParseError.d.ts.map +1 -1
  30. package/dist/ParseError.js +129 -28
  31. package/dist/ParseError.js.map +1 -1
  32. package/dist/Parser.d.ts +375 -29
  33. package/dist/Parser.d.ts.map +1 -1
  34. package/dist/Parser.js +394 -37
  35. package/dist/Parser.js.map +1 -1
  36. package/dist/ParserSchema.d.ts.map +1 -1
  37. package/dist/ParserSchema.js +2 -3
  38. package/dist/ParserSchema.js.map +1 -1
  39. package/dist/ParserStream.d.ts +122 -14
  40. package/dist/ParserStream.d.ts.map +1 -1
  41. package/dist/ParserStream.js +293 -62
  42. package/dist/ParserStream.js.map +1 -1
  43. package/dist/Syntax.d.ts +23 -5
  44. package/dist/Syntax.d.ts.map +1 -1
  45. package/dist/Syntax.js +1 -1
  46. package/dist/Syntax.js.map +1 -1
  47. package/dist/Text.d.ts.map +1 -1
  48. package/dist/Text.js +21 -15
  49. package/dist/Text.js.map +1 -1
  50. package/dist/Token.d.ts +65 -11
  51. package/dist/Token.d.ts.map +1 -1
  52. package/dist/Token.js +73 -26
  53. package/dist/Token.js.map +1 -1
  54. package/dist/internal/compiler/closure.d.ts +1 -1
  55. package/dist/internal/compiler/closure.d.ts.map +1 -1
  56. package/dist/internal/compiler/closure.js +263 -113
  57. package/dist/internal/compiler/closure.js.map +1 -1
  58. package/dist/internal/compiler/entries.d.ts +3 -1
  59. package/dist/internal/compiler/entries.d.ts.map +1 -1
  60. package/dist/internal/compiler/entries.js +3 -2
  61. package/dist/internal/compiler/entries.js.map +1 -1
  62. package/dist/internal/compiler/first.d.ts.map +1 -1
  63. package/dist/internal/compiler/first.js +9 -2
  64. package/dist/internal/compiler/first.js.map +1 -1
  65. package/dist/internal/compiler/graph.d.ts +39 -0
  66. package/dist/internal/compiler/graph.d.ts.map +1 -0
  67. package/dist/internal/compiler/graph.js +78 -0
  68. package/dist/internal/compiler/graph.js.map +1 -0
  69. package/dist/internal/compiler/jit.d.ts +5 -0
  70. package/dist/internal/compiler/jit.d.ts.map +1 -1
  71. package/dist/internal/compiler/jit.js +208 -106
  72. package/dist/internal/compiler/jit.js.map +1 -1
  73. package/dist/internal/compiler/registry.d.ts +11 -3
  74. package/dist/internal/compiler/registry.d.ts.map +1 -1
  75. package/dist/internal/compiler/registry.js.map +1 -1
  76. package/dist/internal/compiler/search.d.ts +32 -0
  77. package/dist/internal/compiler/search.d.ts.map +1 -0
  78. package/dist/internal/compiler/search.js +68 -0
  79. package/dist/internal/compiler/search.js.map +1 -0
  80. package/dist/internal/compiler/sets.d.ts.map +1 -1
  81. package/dist/internal/compiler/sets.js +1 -1
  82. package/dist/internal/compiler/sets.js.map +1 -1
  83. package/dist/internal/compiler/wasm.d.ts +8 -0
  84. package/dist/internal/compiler/wasm.d.ts.map +1 -0
  85. package/dist/internal/compiler/wasm.js +2840 -0
  86. package/dist/internal/compiler/wasm.js.map +1 -0
  87. package/dist/internal/compiler/wasmAsm.d.ts +199 -0
  88. package/dist/internal/compiler/wasmAsm.d.ts.map +1 -0
  89. package/dist/internal/compiler/wasmAsm.js +427 -0
  90. package/dist/internal/compiler/wasmAsm.js.map +1 -0
  91. package/dist/internal/entries.d.ts +3 -1
  92. package/dist/internal/entries.d.ts.map +1 -1
  93. package/dist/internal/entries.js.map +1 -1
  94. package/dist/internal/node.d.ts +77 -3
  95. package/dist/internal/node.d.ts.map +1 -1
  96. package/dist/internal/node.js +54 -10
  97. package/dist/internal/node.js.map +1 -1
  98. package/dist/internal/numbers.d.ts.map +1 -1
  99. package/dist/internal/numbers.js.map +1 -1
  100. package/dist/internal/run.d.ts +14 -0
  101. package/dist/internal/run.d.ts.map +1 -1
  102. package/dist/internal/run.js +53 -11
  103. package/dist/internal/run.js.map +1 -1
  104. package/dist/internal/stack.d.ts +14 -0
  105. package/dist/internal/stack.d.ts.map +1 -1
  106. package/dist/internal/stack.js +24 -2
  107. package/dist/internal/stack.js.map +1 -1
  108. package/dist/internal/syntax.d.ts +4 -4
  109. package/dist/internal/syntax.d.ts.map +1 -1
  110. package/dist/internal/syntax.js +18 -10
  111. package/dist/internal/syntax.js.map +1 -1
  112. package/dist/internal/text.d.ts +32 -0
  113. package/dist/internal/text.d.ts.map +1 -1
  114. package/dist/internal/text.js +68 -14
  115. package/dist/internal/text.js.map +1 -1
  116. package/dist/internal/tokens.d.ts +20 -0
  117. package/dist/internal/tokens.d.ts.map +1 -1
  118. package/dist/internal/tokens.js +72 -39
  119. package/dist/internal/tokens.js.map +1 -1
  120. package/dist/internal/tuple.d.ts +6 -0
  121. package/dist/internal/tuple.d.ts.map +1 -0
  122. package/dist/internal/tuple.js +170 -0
  123. package/dist/internal/tuple.js.map +1 -0
  124. package/package.json +18 -10
  125. package/src/Ascii.ts +47 -12
  126. package/src/Binary.ts +174 -73
  127. package/src/Bits.ts +16 -6
  128. package/src/Compiler.ts +101 -10
  129. package/src/Expression.ts +94 -81
  130. package/src/Input.ts +99 -7
  131. package/src/Nom.ts +15 -5
  132. package/src/ParseError.ts +209 -52
  133. package/src/Parser.ts +684 -143
  134. package/src/ParserSchema.ts +1 -2
  135. package/src/ParserStream.ts +361 -68
  136. package/src/Syntax.ts +60 -36
  137. package/src/Text.ts +32 -27
  138. package/src/Token.ts +152 -34
  139. package/src/internal/compiler/closure.ts +371 -153
  140. package/src/internal/compiler/entries.ts +4 -2
  141. package/src/internal/compiler/first.ts +16 -0
  142. package/src/internal/compiler/graph.ts +96 -0
  143. package/src/internal/compiler/jit.ts +319 -169
  144. package/src/internal/compiler/registry.ts +10 -2
  145. package/src/internal/compiler/search.ts +68 -0
  146. package/src/internal/compiler/sets.ts +7 -3
  147. package/src/internal/compiler/wasm.ts +3276 -0
  148. package/src/internal/compiler/wasmAsm.ts +484 -0
  149. package/src/internal/entries.ts +8 -2
  150. package/src/internal/node.ts +103 -7
  151. package/src/internal/numbers.ts +3 -1
  152. package/src/internal/run.ts +73 -12
  153. package/src/internal/stack.ts +25 -1
  154. package/src/internal/syntax.ts +39 -18
  155. package/src/internal/text.ts +97 -9
  156. package/src/internal/tokens.ts +106 -14
  157. package/src/internal/tuple.ts +141 -0
package/src/Ascii.ts CHANGED
@@ -31,6 +31,7 @@ import type { Parser } from "./Parser.js"
31
31
  import * as P from "./Parser.js"
32
32
  import * as numbers from "./internal/numbers.js"
33
33
  import { Key } from "./internal/key.js"
34
+ import { controlCode } from "./internal/node.js"
34
35
  import * as Resume from "./internal/resume.js"
35
36
  import { codePointPredicate } from "./internal/tokens.js"
36
37
  import { literalImpl, range, takeWhileImpl } from "./Token.js"
@@ -183,8 +184,13 @@ const alnumSet = [/*#__PURE__*/ range("a", "z"), /*#__PURE__*/ range("A", "Z"),
183
184
  const spaceSet = " \t"
184
185
  const multispaceSet = " \t\r\n"
185
186
 
186
- const cls = (set: unknown, min: 0 | 1, description: string, kind: ErrorKind, hint = true): Parser<string> =>
187
- takeWhileImpl<string>(set, { min }, false, `${kind.toLowerCase()}${min}`, description, kind, hint)
187
+ const cls = (
188
+ set: Token.StringTokenSet,
189
+ min: 0 | 1,
190
+ description: string,
191
+ kind: ErrorKind,
192
+ hint = true
193
+ ): Parser<string> => takeWhileImpl<string>(set, { min }, false, `${kind.toLowerCase()}${min}`, description, kind, hint)
188
194
 
189
195
  // -----------------------------------------------------------------------------
190
196
  // Character classes
@@ -359,6 +365,7 @@ export const tillLineEnding: Parser<string> = /*#__PURE__*/ P.described<string>(
359
365
  const isString = state.inputKind === 0
360
366
  let i = start
361
367
  while (i < state.end) {
368
+ // SAFETY: `Ascii` parsers run on strings or `Uint8Array`s, and `isString` is `inputKind === 0`, set only for a string
362
369
  const c = isString ? (input as string).charCodeAt(i) : (input as Uint8Array)[i]
363
370
  if (c === 10) break
364
371
  if (c === 13) {
@@ -368,6 +375,7 @@ export const tillLineEnding: Parser<string> = /*#__PURE__*/ P.described<string>(
368
375
  if (state.partial) return state.incomplete(1, expectedLineEnding)
369
376
  return state.backtrack("LineEnding", expectedLineEnding, i)
370
377
  }
378
+ // SAFETY: `Ascii` parsers run on strings or `Uint8Array`s, and `isString` is `inputKind === 0`, set only for a string
371
379
  const d = isString ? (input as string).charCodeAt(i + 1) : (input as Uint8Array)[i + 1]
372
380
  if (d === 10) break
373
381
  return state.backtrack("LineEnding", expectedLineEnding, i)
@@ -376,6 +384,7 @@ export const tillLineEnding: Parser<string> = /*#__PURE__*/ P.described<string>(
376
384
  }
377
385
  if (i >= state.end && state.partial) return state.incomplete(1, expectedLineEnding)
378
386
  state.pos = i
387
+ // SAFETY: on string input, the input type of a `Parser<string>`, the slice is a string (`bytes` retypes it for bytes)
379
388
  return state.slice(start, i) as string
380
389
  },
381
390
  "tillLineEnding",
@@ -471,7 +480,7 @@ const unsignedFloat = /*#__PURE__*/ P.alt(
471
480
  const floatText = /*#__PURE__*/ numbers.floatText(
472
481
  /*#__PURE__*/ P.taken(
473
482
  /*#__PURE__*/ P.alt(/*#__PURE__*/ literalImpl("nan", true), /*#__PURE__*/ P.seq(sign, unsignedFloat))
474
- ).pipe(/*#__PURE__*/ P.expected("number")) as Parser<string>
483
+ ).pipe(/*#__PURE__*/ P.expected("number"))
475
484
  )
476
485
 
477
486
  /** @internal */
@@ -501,6 +510,17 @@ export const float: Parser<number> = /*#__PURE__*/ floatText.pipe(
501
510
  // Literals
502
511
  // -----------------------------------------------------------------------------
503
512
 
513
+ /**
514
+ * The overloads of {@link caseless}.
515
+ *
516
+ * @since 0.5.0
517
+ * @category literals
518
+ */
519
+ export interface CaselessFn {
520
+ (literal: string): Parser<string>
521
+ (literal: Uint8Array): Parser<Uint8Array, never, never, Uint8Array>
522
+ }
523
+
504
524
  /**
505
525
  * Matches a literal ignoring ASCII case (winnow's `Caseless`, nom's
506
526
  * `tag_no_case`). Returns the input as written.
@@ -508,20 +528,19 @@ export const float: Parser<number> = /*#__PURE__*/ floatText.pipe(
508
528
  * @since 0.1.0
509
529
  * @category literals
510
530
  */
511
- export const caseless: {
512
- (literal: string): Parser<string>
513
- (literal: Uint8Array): Parser<Uint8Array, never, never, Uint8Array>
514
- } = (lit: string | Uint8Array): any => literalImpl(lit, true)
531
+ export const caseless: CaselessFn = (lit: string | Uint8Array): any => literalImpl(lit, true)
515
532
 
516
533
  // -----------------------------------------------------------------------------
517
534
  // Escapes
518
535
  // -----------------------------------------------------------------------------
519
536
 
520
- const controlCode = (c: string | number): number => (Predicate.isNumber(c) ? c : c.charCodeAt(0))
537
+ /** The first UTF-16 unit of a control character: what `atControl` compares with a string's units. */
538
+ const firstUnit = (s: string): number => s.charCodeAt(0)
521
539
 
522
540
  const atControl = (state: State<Input>, control: number): boolean => {
523
541
  if (state.pos >= state.end) return false
524
542
  const input = state.input
543
+ // SAFETY: escapes are read from strings or `Uint8Array`s, and `inputKind` 0 is set only for a string
525
544
  const c = state.inputKind === 0 ? (input as string).charCodeAt(state.pos) : (input as Uint8Array)[state.pos]
526
545
  return c === control
527
546
  }
@@ -550,7 +569,7 @@ export const takeEscaped = <A, E, R, I extends Input, B, E2, R2, I2 extends Inpu
550
569
  control: string | number,
551
570
  escapable: Parser<B, E2, R2, I2>
552
571
  ): Parser<Slice<I & I2>, E | E2, R | R2, I & I2> => {
553
- const ctrl = controlCode(control)
572
+ const ctrl = controlCode(control, firstUnit)
554
573
  return takeEscapedImpl(
555
574
  normal,
556
575
  (state) => {
@@ -559,6 +578,7 @@ export const takeEscaped = <A, E, R, I extends Input, B, E2, R2, I2 extends Inpu
559
578
  return true
560
579
  },
561
580
  escapable,
581
+ // SAFETY: the parser runs on input of type `I & I2`, and `State.slice` returns that input's own slice type
562
582
  (state, start) => state.slice(start, state.pos) as Slice<I & I2>,
563
583
  ctrl,
564
584
  undefined
@@ -593,9 +613,11 @@ export const takeEscapedImpl = <A, E, R, I extends Input, B, E2, R2, I2 extends
593
613
  const start = state.pos
594
614
  let resumed = false
595
615
  if (state.resume !== undefined) {
616
+ // SAFETY: under its own `key`, this parser saves only a resume offset (`suspendAt`) or a `Done`
596
617
  const checkpoint = Resume.take(state, key, start) as number | Resume.Done | undefined
597
618
  if (checkpoint instanceof Resume.Done) {
598
619
  state.pos = checkpoint.pos
620
+ // SAFETY: a `Done` saved under this parser's `key` holds the `S` it returned
599
621
  return checkpoint.value as S
600
622
  }
601
623
  if (checkpoint !== undefined) {
@@ -664,7 +686,7 @@ export const escaped = <E, R, I extends Input, E2, R2, I2 extends Input>(
664
686
  control: string | number,
665
687
  escape: Parser<string, E2, R2, I2>
666
688
  ): Parser<string, E | E2, R | R2, I & I2> => {
667
- const ctrl = controlCode(control)
689
+ const ctrl = controlCode(control, firstUnit)
668
690
  return escapedImpl(
669
691
  normal,
670
692
  (state) => {
@@ -701,9 +723,11 @@ export const escapedImpl = <E, R, I extends Input, E2, R2, I2 extends Input>(
701
723
  let out = ""
702
724
  let resumed = false
703
725
  if (state.resume !== undefined) {
726
+ // SAFETY: under its own `key`, this parser saves only an `EscapedCheckpoint` (`suspendAt`) or a `Done`
704
727
  const checkpoint = Resume.take(state, key, start) as EscapedCheckpoint | Resume.Done | undefined
705
728
  if (checkpoint instanceof Resume.Done) {
706
729
  state.pos = checkpoint.pos
730
+ // SAFETY: a `Done` saved under this parser's `key` holds the string it returned
707
731
  return checkpoint.value as string
708
732
  }
709
733
  if (checkpoint !== undefined) {
@@ -754,6 +778,14 @@ type Bytes<P> =
754
778
  ? Parser<A extends string ? Uint8Array : A, E, R, Uint8Array>
755
779
  : never
756
780
 
781
+ /**
782
+ * Gives a value the type of another view of the same parsers (see
783
+ * {@link bytes}).
784
+ */
785
+ const retype = <T>(value: unknown): T =>
786
+ // SAFETY: callers pass the byte views' parsers, which are the string parsers themselves (below)
787
+ value as T
788
+
757
789
  /**
758
790
  * The parsers of this module, typed for `Uint8Array` input. Slices are
759
791
  * returned as `Uint8Array`s.
@@ -803,7 +835,7 @@ export const bytes: {
803
835
  const lit = (s: string) => Token.literal(new TextEncoder().encode(s))
804
836
  const crlfB = lit("\r\n")
805
837
  const newlineB = lit("\n")
806
- return {
838
+ const views = {
807
839
  alpha0,
808
840
  alpha1,
809
841
  digit0,
@@ -829,5 +861,8 @@ export const bytes: {
829
861
  decBigInt,
830
862
  hexUint,
831
863
  float
832
- } as any
864
+ }
865
+ // SAFETY: the string parsers run on byte input unchanged (their slices of a `Uint8Array` are `Uint8Array`s),
866
+ // so only their declared input and slice types differ from the byte views'
867
+ return retype<typeof bytes>(views)
833
868
  })()
package/src/Binary.ts CHANGED
@@ -44,45 +44,61 @@ export type Endianness = "big" | "little" | "native"
44
44
 
45
45
  const nativeLittle = /*#__PURE__*/ new Uint8Array(/*#__PURE__*/ new Uint16Array([1]).buffer)[0] === 1
46
46
 
47
- const fixed = <A>(
48
- size: number,
49
- name: string,
50
- read: (view: DataView, offset: number) => A
51
- ): Parser<A, never, never, Uint8Array> => {
47
+ /** Reads a number at an offset of a state's bytes. */
48
+ type Read<A> = (bytes: Uint8Array, offset: number, state: State<Uint8Array>) => A
49
+
50
+ /**
51
+ * A `DataView` of a state's bytes, for the numbers integer arithmetic can't
52
+ * read: created once per state (whose input never changes), as one per
53
+ * read cost most of a number's time.
54
+ */
55
+ const viewOf = (state: State<Uint8Array>): DataView => {
56
+ const b = state.input
57
+ return (state.view ??= new DataView(b.buffer, b.byteOffset, b.byteLength))
58
+ }
59
+
60
+ /** A {@link Read} through the state's view. */
61
+ const viewed =
62
+ <A>(read: (view: DataView, offset: number) => A): Read<A> =>
63
+ (_, o, s) =>
64
+ read(viewOf(s), o)
65
+
66
+ const fixed = <A>(size: number, name: string, read: Read<A>): Parser<A, never, never, Uint8Array> => {
52
67
  const expected = [`${size} byte${size === 1 ? "" : "s"}`]
53
- return P.make((state: State<Uint8Array>) => {
54
- const pos = state.pos
55
- const avail = state.end - pos
56
- if (avail < size) return state.eoi("Token", expected, size - avail)
57
- const bytes = state.input
58
- const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength)
59
- state.pos = pos + size
60
- return read(view, pos)
61
- }, name)
68
+ return P.described(
69
+ (state: State<Uint8Array>) => {
70
+ const pos = state.pos
71
+ const avail = state.end - pos
72
+ if (avail < size) return state.eoi("Token", expected, size - avail)
73
+ state.pos = pos + size
74
+ return read(state.input, pos, state)
75
+ },
76
+ name,
77
+ { _tag: "Fixed", size, read }
78
+ )
62
79
  }
63
80
 
64
- const readU24 = (little: boolean) => (v: DataView, o: number) =>
65
- little
66
- ? v.getUint8(o) | (v.getUint8(o + 1) << 8) | (v.getUint8(o + 2) << 16)
67
- : (v.getUint8(o) << 16) | (v.getUint8(o + 1) << 8) | v.getUint8(o + 2)
81
+ // Integers of up to 32 bits are read from the bytes; the rest through a view
82
+ const readU24 =
83
+ (little: boolean): Read<number> =>
84
+ (b, o) =>
85
+ little ? b[o] | (b[o + 1] << 8) | (b[o + 2] << 16) : (b[o] << 16) | (b[o + 1] << 8) | b[o + 2]
68
86
 
69
- const readI24 = (little: boolean) => {
87
+ const readI24 = (little: boolean): Read<number> => {
70
88
  const u = readU24(little)
71
- return (v: DataView, o: number) => {
72
- const x = u(v, o)
73
- return x & 0x800000 ? x - 0x1000000 : x
74
- }
89
+ return (b, o, s) => (u(b, o, s) << 8) >> 8
75
90
  }
76
91
 
77
- const readU128 = (little: boolean) => (v: DataView, o: number) => {
78
- const a = v.getBigUint64(o, little)
79
- const b = v.getBigUint64(o + 8, little)
80
- return little ? (b << 64n) | a : (a << 64n) | b
81
- }
92
+ const readU128 = (little: boolean): Read<bigint> =>
93
+ viewed((v, o) => {
94
+ const a = v.getBigUint64(o, little)
95
+ const b = v.getBigUint64(o + 8, little)
96
+ return little ? (b << 64n) | a : (a << 64n) | b
97
+ })
82
98
 
83
- const readI128 = (little: boolean) => {
99
+ const readI128 = (little: boolean): Read<bigint> => {
84
100
  const u = readU128(little)
85
- return (v: DataView, o: number) => BigInt.asIntN(128, u(v, o))
101
+ return (b, o, s) => BigInt.asIntN(128, u(b, o, s))
86
102
  }
87
103
 
88
104
  /**
@@ -91,7 +107,7 @@ const readI128 = (little: boolean) => {
91
107
  * @since 0.1.0
92
108
  * @category integers
93
109
  */
94
- export const u8: Parser<number, never, never, Uint8Array> = /*#__PURE__*/ fixed(1, "u8", (v, o) => v.getUint8(o))
110
+ export const u8: Parser<number, never, never, Uint8Array> = /*#__PURE__*/ fixed(1, "u8", (b, o) => b[o])
95
111
 
96
112
  /**
97
113
  * A signed byte.
@@ -99,23 +115,31 @@ export const u8: Parser<number, never, never, Uint8Array> = /*#__PURE__*/ fixed(
99
115
  * @since 0.1.0
100
116
  * @category integers
101
117
  */
102
- export const i8: Parser<number, never, never, Uint8Array> = /*#__PURE__*/ fixed(1, "i8", (v, o) => v.getInt8(o))
118
+ export const i8: Parser<number, never, never, Uint8Array> = /*#__PURE__*/ fixed(1, "i8", (b, o) => (b[o] << 24) >> 24)
103
119
 
104
120
  /** @since 0.1.0 @category integers */
105
- export const beU16: Parser<number, never, never, Uint8Array> = /*#__PURE__*/ fixed(2, "beU16", (v, o) =>
106
- v.getUint16(o, false)
121
+ export const beU16: Parser<number, never, never, Uint8Array> = /*#__PURE__*/ fixed(
122
+ 2,
123
+ "beU16",
124
+ (b, o) => (b[o] << 8) | b[o + 1]
107
125
  )
108
126
  /** @since 0.1.0 @category integers */
109
- export const leU16: Parser<number, never, never, Uint8Array> = /*#__PURE__*/ fixed(2, "leU16", (v, o) =>
110
- v.getUint16(o, true)
127
+ export const leU16: Parser<number, never, never, Uint8Array> = /*#__PURE__*/ fixed(
128
+ 2,
129
+ "leU16",
130
+ (b, o) => b[o] | (b[o + 1] << 8)
111
131
  )
112
132
  /** @since 0.1.0 @category integers */
113
- export const beI16: Parser<number, never, never, Uint8Array> = /*#__PURE__*/ fixed(2, "beI16", (v, o) =>
114
- v.getInt16(o, false)
133
+ export const beI16: Parser<number, never, never, Uint8Array> = /*#__PURE__*/ fixed(
134
+ 2,
135
+ "beI16",
136
+ (b, o) => (((b[o] << 8) | b[o + 1]) << 16) >> 16
115
137
  )
116
138
  /** @since 0.1.0 @category integers */
117
- export const leI16: Parser<number, never, never, Uint8Array> = /*#__PURE__*/ fixed(2, "leI16", (v, o) =>
118
- v.getInt16(o, true)
139
+ export const leI16: Parser<number, never, never, Uint8Array> = /*#__PURE__*/ fixed(
140
+ 2,
141
+ "leI16",
142
+ (b, o) => ((b[o] | (b[o + 1] << 8)) << 16) >> 16
119
143
  )
120
144
  /** @since 0.1.0 @category integers */
121
145
  export const beU24: Parser<number, never, never, Uint8Array> = /*#__PURE__*/ fixed(
@@ -142,36 +166,52 @@ export const leI24: Parser<number, never, never, Uint8Array> = /*#__PURE__*/ fix
142
166
  /*#__PURE__*/ readI24(true)
143
167
  )
144
168
  /** @since 0.1.0 @category integers */
145
- export const beU32: Parser<number, never, never, Uint8Array> = /*#__PURE__*/ fixed(4, "beU32", (v, o) =>
146
- v.getUint32(o, false)
169
+ export const beU32: Parser<number, never, never, Uint8Array> = /*#__PURE__*/ fixed(
170
+ 4,
171
+ "beU32",
172
+ (b, o) => ((b[o] << 24) | (b[o + 1] << 16) | (b[o + 2] << 8) | b[o + 3]) >>> 0
147
173
  )
148
174
  /** @since 0.1.0 @category integers */
149
- export const leU32: Parser<number, never, never, Uint8Array> = /*#__PURE__*/ fixed(4, "leU32", (v, o) =>
150
- v.getUint32(o, true)
175
+ export const leU32: Parser<number, never, never, Uint8Array> = /*#__PURE__*/ fixed(
176
+ 4,
177
+ "leU32",
178
+ (b, o) => (b[o] | (b[o + 1] << 8) | (b[o + 2] << 16) | (b[o + 3] << 24)) >>> 0
151
179
  )
152
180
  /** @since 0.1.0 @category integers */
153
- export const beI32: Parser<number, never, never, Uint8Array> = /*#__PURE__*/ fixed(4, "beI32", (v, o) =>
154
- v.getInt32(o, false)
181
+ export const beI32: Parser<number, never, never, Uint8Array> = /*#__PURE__*/ fixed(
182
+ 4,
183
+ "beI32",
184
+ (b, o) => (b[o] << 24) | (b[o + 1] << 16) | (b[o + 2] << 8) | b[o + 3]
155
185
  )
156
186
  /** @since 0.1.0 @category integers */
157
- export const leI32: Parser<number, never, never, Uint8Array> = /*#__PURE__*/ fixed(4, "leI32", (v, o) =>
158
- v.getInt32(o, true)
187
+ export const leI32: Parser<number, never, never, Uint8Array> = /*#__PURE__*/ fixed(
188
+ 4,
189
+ "leI32",
190
+ (b, o) => b[o] | (b[o + 1] << 8) | (b[o + 2] << 16) | (b[o + 3] << 24)
159
191
  )
160
192
  /** @since 0.1.0 @category integers */
161
- export const beU64: Parser<bigint, never, never, Uint8Array> = /*#__PURE__*/ fixed(8, "beU64", (v, o) =>
162
- v.getBigUint64(o, false)
193
+ export const beU64: Parser<bigint, never, never, Uint8Array> = /*#__PURE__*/ fixed(
194
+ 8,
195
+ "beU64",
196
+ /*#__PURE__*/ viewed((v, o) => v.getBigUint64(o, false))
163
197
  )
164
198
  /** @since 0.1.0 @category integers */
165
- export const leU64: Parser<bigint, never, never, Uint8Array> = /*#__PURE__*/ fixed(8, "leU64", (v, o) =>
166
- v.getBigUint64(o, true)
199
+ export const leU64: Parser<bigint, never, never, Uint8Array> = /*#__PURE__*/ fixed(
200
+ 8,
201
+ "leU64",
202
+ /*#__PURE__*/ viewed((v, o) => v.getBigUint64(o, true))
167
203
  )
168
204
  /** @since 0.1.0 @category integers */
169
- export const beI64: Parser<bigint, never, never, Uint8Array> = /*#__PURE__*/ fixed(8, "beI64", (v, o) =>
170
- v.getBigInt64(o, false)
205
+ export const beI64: Parser<bigint, never, never, Uint8Array> = /*#__PURE__*/ fixed(
206
+ 8,
207
+ "beI64",
208
+ /*#__PURE__*/ viewed((v, o) => v.getBigInt64(o, false))
171
209
  )
172
210
  /** @since 0.1.0 @category integers */
173
- export const leI64: Parser<bigint, never, never, Uint8Array> = /*#__PURE__*/ fixed(8, "leI64", (v, o) =>
174
- v.getBigInt64(o, true)
211
+ export const leI64: Parser<bigint, never, never, Uint8Array> = /*#__PURE__*/ fixed(
212
+ 8,
213
+ "leI64",
214
+ /*#__PURE__*/ viewed((v, o) => v.getBigInt64(o, true))
175
215
  )
176
216
  /** @since 0.1.0 @category integers */
177
217
  export const beU128: Parser<bigint, never, never, Uint8Array> = /*#__PURE__*/ fixed(
@@ -199,20 +239,28 @@ export const leI128: Parser<bigint, never, never, Uint8Array> = /*#__PURE__*/ fi
199
239
  )
200
240
 
201
241
  /** @since 0.1.0 @category floats */
202
- export const beF32: Parser<number, never, never, Uint8Array> = /*#__PURE__*/ fixed(4, "beF32", (v, o) =>
203
- v.getFloat32(o, false)
242
+ export const beF32: Parser<number, never, never, Uint8Array> = /*#__PURE__*/ fixed(
243
+ 4,
244
+ "beF32",
245
+ /*#__PURE__*/ viewed((v, o) => v.getFloat32(o, false))
204
246
  )
205
247
  /** @since 0.1.0 @category floats */
206
- export const leF32: Parser<number, never, never, Uint8Array> = /*#__PURE__*/ fixed(4, "leF32", (v, o) =>
207
- v.getFloat32(o, true)
248
+ export const leF32: Parser<number, never, never, Uint8Array> = /*#__PURE__*/ fixed(
249
+ 4,
250
+ "leF32",
251
+ /*#__PURE__*/ viewed((v, o) => v.getFloat32(o, true))
208
252
  )
209
253
  /** @since 0.1.0 @category floats */
210
- export const beF64: Parser<number, never, never, Uint8Array> = /*#__PURE__*/ fixed(8, "beF64", (v, o) =>
211
- v.getFloat64(o, false)
254
+ export const beF64: Parser<number, never, never, Uint8Array> = /*#__PURE__*/ fixed(
255
+ 8,
256
+ "beF64",
257
+ /*#__PURE__*/ viewed((v, o) => v.getFloat64(o, false))
212
258
  )
213
259
  /** @since 0.1.0 @category floats */
214
- export const leF64: Parser<number, never, never, Uint8Array> = /*#__PURE__*/ fixed(8, "leF64", (v, o) =>
215
- v.getFloat64(o, true)
260
+ export const leF64: Parser<number, never, never, Uint8Array> = /*#__PURE__*/ fixed(
261
+ 8,
262
+ "leF64",
263
+ /*#__PURE__*/ viewed((v, o) => v.getFloat64(o, true))
216
264
  )
217
265
 
218
266
  const byEndian =
@@ -286,11 +334,46 @@ export const f64: (endian: Endianness) => Parser<number, never, never, Uint8Arra
286
334
  leF64
287
335
  )
288
336
 
337
+ const MaxCount = /*#__PURE__*/ BigInt(Number.MAX_SAFE_INTEGER)
338
+
339
+ /**
340
+ * Whether a length read from the input is a count: a non-negative integer
341
+ * of at most `Number.MAX_SAFE_INTEGER`. A signed, fractional or `NaN`
342
+ * length is the input's to choose, so it fails the parse where it was read
343
+ * (as `Parser.filter` does) and never reaches `Token.take` or
344
+ * `Parser.repeat`, which throw on one.
345
+ */
346
+ const isCount = (n: number | bigint): boolean =>
347
+ Predicate.isBigInt(n) ? n >= 0n && n <= MaxCount : Number.isSafeInteger(n) && n >= 0
348
+
289
349
  const toCount = (n: number | bigint): number => (Predicate.isBigInt(n) ? Number(n) : n)
290
350
 
351
+ /** How many counts {@link byCount} keeps the parsers of. */
352
+ const CountCache = 256
353
+
354
+ /**
355
+ * `build`, keeping the parsers it built for the first {@link CountCache}
356
+ * counts seen: length-prefixed data repeats a few lengths, and building a
357
+ * parser per value cost several times the parse.
358
+ */
359
+ const byCount = <A>(build: (n: number) => A): ((n: number | bigint) => A) => {
360
+ const built = new Map<number, A>()
361
+ return (n) => {
362
+ const count = toCount(n)
363
+ let a = built.get(count)
364
+ if (a === undefined) {
365
+ a = build(count)
366
+ if (built.size < CountCache) built.set(count, a)
367
+ }
368
+ return a
369
+ }
370
+ }
371
+
291
372
  /**
292
373
  * Parses a length with `count`, then takes that many tokens (winnow's
293
- * `length_take`, nom's `length_data`).
374
+ * `length_take`, nom's `length_data`). A length that is not a non-negative
375
+ * integer (a negative `i8`, a fractional float) fails the parse at the
376
+ * length.
294
377
  *
295
378
  * @since 0.1.0
296
379
  * @category length-prefixed
@@ -298,17 +381,31 @@ const toCount = (n: number | bigint): number => (Predicate.isBigInt(n) ? Number(
298
381
  export const lengthTake = <E, R, I extends Input = Uint8Array>(
299
382
  count: Parser<number | bigint, E, R, I>
300
383
  ): Parser<Slice<I>, E, R, I> => {
301
- const countStep = count.step
302
- return P.make((state: State<I>) => {
303
- const n = countStep(state)
304
- if (n === Fail) return Fail
305
- return (Token.take<I>(toCount(n)).step as any)(state)
306
- }, "lengthTake")
384
+ const take = byCount((n) => Token.take<I>(n).step)
385
+ // The count compiles with the grammar: the step is built from its (see `Parser.combinator`)
386
+ return P.combinator(
387
+ [count],
388
+ ([countStep]) =>
389
+ (state: State<I>) => {
390
+ const start = state.pos
391
+ // SAFETY: the count parser's step, interpreted or compiled, returns its output (a number or bigint) or `Fail`
392
+ const n = countStep(state) as number | bigint | Fail
393
+ if (n === Fail) return Fail
394
+ // As `Parser.filter` fails: at the length, backtracking
395
+ if (!isCount(n)) {
396
+ state.rewind(start)
397
+ return state.backtrack("Verify")
398
+ }
399
+ return take(n)(state)
400
+ },
401
+ "lengthTake"
402
+ )
307
403
  }
308
404
 
309
405
  /**
310
406
  * Parses a length with `count`, then applies `parser` that many times
311
- * (winnow's `length_repeat`, nom's `length_count`).
407
+ * (winnow's `length_repeat`, nom's `length_count`). A length that is not a
408
+ * non-negative integer fails the parse at the length.
312
409
  *
313
410
  * @since 0.1.0
314
411
  * @category length-prefixed
@@ -317,7 +414,10 @@ export const lengthRepeat = <E, R, I extends Input, A, E2, R2, I2 extends Input>
317
414
  count: Parser<number | bigint, E, R, I>,
318
415
  parser: Parser<A, E2, R2, I2>
319
416
  ): Parser<Array<A>, E | E2, R | R2, I & I2> =>
320
- P.flatMap(count, (n) => P.repeat(parser, toCount(n)) as Parser<Array<A>, E2, R2, I2>)
417
+ P.flatMap(
418
+ P.filter(count, isCount),
419
+ byCount((n) => P.repeat(parser, n))
420
+ )
321
421
 
322
422
  /**
323
423
  * Parses a length with `count`, takes that many tokens, and applies
@@ -331,4 +431,5 @@ export const lengthAndThen = <E, R, I extends Input, A, E2, R2>(
331
431
  count: Parser<number | bigint, E, R, I>,
332
432
  parser: Parser<A, E2, R2, Slice<I> & Input>
333
433
  ): Parser<A, E | E2, R | R2, I> =>
434
+ // SAFETY: a slice of an input is itself an input (a string, a `Uint8Array` or an array), so `Slice<I>` is an `Input`
334
435
  P.mapParser(lengthTake(count) as Parser<Slice<I> & Input, E, R, I>, P.complete(parser))
package/src/Bits.ts CHANGED
@@ -24,6 +24,7 @@ import type { State } from "./Input.js"
24
24
  import { Fail, Failure } from "./Input.js"
25
25
  import type { Parser } from "./Parser.js"
26
26
  import { make } from "./Parser.js"
27
+ import { checkCount } from "./internal/node.js"
27
28
 
28
29
  declare const BitInputBrand: unique symbol
29
30
 
@@ -93,7 +94,7 @@ const inUnits = <A>(state: State<any>, factor: number, step: (state: State<any>)
93
94
  * @category combinators
94
95
  */
95
96
  export const bits = <A, E, R>(self: Parser<A, E, R, BitInput>): Parser<A, E, R, Uint8Array> => {
96
- const step = self.step as unknown as (state: State<any>) => A | Fail
97
+ const step = self.step
97
98
  return make((state) => inUnits(state, 8, step), "bits")
98
99
  }
99
100
 
@@ -105,18 +106,24 @@ export const bits = <A, E, R>(self: Parser<A, E, R, BitInput>): Parser<A, E, R,
105
106
  * @category combinators
106
107
  */
107
108
  export const bytes = <A, E, R>(self: Parser<A, E, R, Uint8Array>): Parser<A, E, R, BitInput> => {
108
- const step = self.step as (state: State<any>) => A | Fail
109
+ const step = self.step
109
110
  return make((state: State<any>) => inUnits(state, 1 / 8, step), "bytes")
110
111
  }
111
112
 
112
113
  const readBits = (state: State<any>, count: number): number => {
114
+ // SAFETY: bit-level parsers run inside `bits`, on the `Uint8Array` it was given (`BitInput` only re-indexes it in bits)
113
115
  const input = state.input as Uint8Array
114
116
  let pos = state.pos
115
117
  let value = 0
116
- for (let i = 0; i < count; i++) {
117
- const byte = input[pos >>> 3]
118
- value = value * 2 + ((byte >>> (7 - (pos & 7))) & 1)
119
- pos++
118
+ let left = count
119
+ // As many bits as the current byte holds at once, not one at a time
120
+ while (left > 0) {
121
+ const avail = 8 - (pos & 7)
122
+ const n = left < avail ? left : avail
123
+ // Multiplying, not shifting: the value can have up to 53 bits
124
+ value = value * (1 << n) + ((input[pos >>> 3] >>> (avail - n)) & ((1 << n) - 1))
125
+ pos += n
126
+ left -= n
120
127
  }
121
128
  state.pos = pos
122
129
  return value
@@ -130,6 +137,7 @@ const readBits = (state: State<any>, count: number): number => {
130
137
  * @category parsers
131
138
  */
132
139
  export const take = (count: number): Parser<number, never, never, BitInput> => {
140
+ checkCount(count)
133
141
  if (count > 53) throw new RangeError("Bits.take: use Bits.takeBig for more than 53 bits")
134
142
  const expected = [`${count} bits`]
135
143
  return make((state: State<any>) => {
@@ -146,6 +154,7 @@ export const take = (count: number): Parser<number, never, never, BitInput> => {
146
154
  * @category parsers
147
155
  */
148
156
  export const takeBig = (count: number): Parser<bigint, never, never, BitInput> => {
157
+ checkCount(count)
149
158
  const expected = [`${count} bits`]
150
159
  return make((state: State<any>) => {
151
160
  const avail = state.end - state.pos
@@ -169,6 +178,7 @@ export const takeBig = (count: number): Parser<bigint, never, never, BitInput> =
169
178
  * @category parsers
170
179
  */
171
180
  export const pattern = (pattern: number, count: number): Parser<number, never, never, BitInput> => {
181
+ checkCount(count)
172
182
  const expected = [`bit pattern ${pattern.toString(2).padStart(count, "0")}`]
173
183
  return make((state: State<any>) => {
174
184
  const start = state.pos