@thomasfosterau/effect-parse 0.4.0 → 0.5.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 (148) hide show
  1. package/README.md +40 -14
  2. package/dist/Ascii.d.ts +11 -4
  3. package/dist/Ascii.d.ts.map +1 -1
  4. package/dist/Ascii.js +7 -1
  5. package/dist/Ascii.js.map +1 -1
  6. package/dist/Binary.d.ts.map +1 -1
  7. package/dist/Binary.js +64 -35
  8. package/dist/Binary.js.map +1 -1
  9. package/dist/Bits.d.ts.map +1 -1
  10. package/dist/Bits.js +7 -4
  11. package/dist/Bits.js.map +1 -1
  12. package/dist/Compiler.d.ts +74 -2
  13. package/dist/Compiler.d.ts.map +1 -1
  14. package/dist/Compiler.js +78 -1
  15. package/dist/Compiler.js.map +1 -1
  16. package/dist/Expression.d.ts.map +1 -1
  17. package/dist/Expression.js +86 -74
  18. package/dist/Expression.js.map +1 -1
  19. package/dist/Input.d.ts +37 -4
  20. package/dist/Input.d.ts.map +1 -1
  21. package/dist/Input.js +42 -0
  22. package/dist/Input.js.map +1 -1
  23. package/dist/Nom.d.ts +5 -15
  24. package/dist/Nom.d.ts.map +1 -1
  25. package/dist/Nom.js.map +1 -1
  26. package/dist/ParseError.d.ts +2 -2
  27. package/dist/ParseError.d.ts.map +1 -1
  28. package/dist/ParseError.js +1 -1
  29. package/dist/ParseError.js.map +1 -1
  30. package/dist/Parser.d.ts +370 -28
  31. package/dist/Parser.d.ts.map +1 -1
  32. package/dist/Parser.js +390 -19
  33. package/dist/Parser.js.map +1 -1
  34. package/dist/ParserSchema.js +2 -2
  35. package/dist/ParserSchema.js.map +1 -1
  36. package/dist/ParserStream.d.ts +3 -2
  37. package/dist/ParserStream.d.ts.map +1 -1
  38. package/dist/ParserStream.js +15 -10
  39. package/dist/ParserStream.js.map +1 -1
  40. package/dist/Syntax.d.ts +23 -5
  41. package/dist/Syntax.d.ts.map +1 -1
  42. package/dist/Syntax.js.map +1 -1
  43. package/dist/Text.d.ts.map +1 -1
  44. package/dist/Text.js +9 -9
  45. package/dist/Text.js.map +1 -1
  46. package/dist/Token.d.ts +55 -10
  47. package/dist/Token.d.ts.map +1 -1
  48. package/dist/Token.js +59 -21
  49. package/dist/Token.js.map +1 -1
  50. package/dist/internal/compiler/closure.d.ts.map +1 -1
  51. package/dist/internal/compiler/closure.js +257 -97
  52. package/dist/internal/compiler/closure.js.map +1 -1
  53. package/dist/internal/compiler/entries.d.ts +3 -1
  54. package/dist/internal/compiler/entries.d.ts.map +1 -1
  55. package/dist/internal/compiler/entries.js +3 -2
  56. package/dist/internal/compiler/entries.js.map +1 -1
  57. package/dist/internal/compiler/first.d.ts.map +1 -1
  58. package/dist/internal/compiler/first.js +7 -1
  59. package/dist/internal/compiler/first.js.map +1 -1
  60. package/dist/internal/compiler/graph.d.ts +17 -0
  61. package/dist/internal/compiler/graph.d.ts.map +1 -0
  62. package/dist/internal/compiler/graph.js +56 -0
  63. package/dist/internal/compiler/graph.js.map +1 -0
  64. package/dist/internal/compiler/jit.d.ts +5 -0
  65. package/dist/internal/compiler/jit.d.ts.map +1 -1
  66. package/dist/internal/compiler/jit.js +185 -96
  67. package/dist/internal/compiler/jit.js.map +1 -1
  68. package/dist/internal/compiler/registry.d.ts +10 -2
  69. package/dist/internal/compiler/registry.d.ts.map +1 -1
  70. package/dist/internal/compiler/registry.js.map +1 -1
  71. package/dist/internal/compiler/search.d.ts +32 -0
  72. package/dist/internal/compiler/search.d.ts.map +1 -0
  73. package/dist/internal/compiler/search.js +68 -0
  74. package/dist/internal/compiler/search.js.map +1 -0
  75. package/dist/internal/compiler/sets.d.ts.map +1 -1
  76. package/dist/internal/compiler/sets.js +1 -1
  77. package/dist/internal/compiler/sets.js.map +1 -1
  78. package/dist/internal/compiler/wasm.d.ts +8 -0
  79. package/dist/internal/compiler/wasm.d.ts.map +1 -0
  80. package/dist/internal/compiler/wasm.js +2769 -0
  81. package/dist/internal/compiler/wasm.js.map +1 -0
  82. package/dist/internal/compiler/wasmAsm.d.ts +199 -0
  83. package/dist/internal/compiler/wasmAsm.d.ts.map +1 -0
  84. package/dist/internal/compiler/wasmAsm.js +427 -0
  85. package/dist/internal/compiler/wasmAsm.js.map +1 -0
  86. package/dist/internal/entries.d.ts +3 -1
  87. package/dist/internal/entries.d.ts.map +1 -1
  88. package/dist/internal/entries.js +1 -2
  89. package/dist/internal/entries.js.map +1 -1
  90. package/dist/internal/node.d.ts +58 -2
  91. package/dist/internal/node.d.ts.map +1 -1
  92. package/dist/internal/node.js +13 -3
  93. package/dist/internal/node.js.map +1 -1
  94. package/dist/internal/numbers.d.ts.map +1 -1
  95. package/dist/internal/numbers.js.map +1 -1
  96. package/dist/internal/run.d.ts +12 -0
  97. package/dist/internal/run.d.ts.map +1 -1
  98. package/dist/internal/run.js +51 -11
  99. package/dist/internal/run.js.map +1 -1
  100. package/dist/internal/stack.d.ts +6 -0
  101. package/dist/internal/stack.d.ts.map +1 -1
  102. package/dist/internal/stack.js +8 -2
  103. package/dist/internal/stack.js.map +1 -1
  104. package/dist/internal/syntax.d.ts +4 -4
  105. package/dist/internal/syntax.d.ts.map +1 -1
  106. package/dist/internal/syntax.js +18 -10
  107. package/dist/internal/syntax.js.map +1 -1
  108. package/dist/internal/text.d.ts +32 -0
  109. package/dist/internal/text.d.ts.map +1 -1
  110. package/dist/internal/text.js +68 -14
  111. package/dist/internal/text.js.map +1 -1
  112. package/dist/internal/tokens.d.ts +13 -0
  113. package/dist/internal/tokens.d.ts.map +1 -1
  114. package/dist/internal/tokens.js +59 -39
  115. package/dist/internal/tokens.js.map +1 -1
  116. package/package.json +5 -4
  117. package/src/Ascii.ts +42 -9
  118. package/src/Binary.ts +151 -71
  119. package/src/Bits.ts +12 -6
  120. package/src/Compiler.ts +101 -10
  121. package/src/Expression.ts +94 -81
  122. package/src/Input.ts +65 -6
  123. package/src/Nom.ts +15 -5
  124. package/src/ParseError.ts +9 -4
  125. package/src/Parser.ts +682 -129
  126. package/src/ParserSchema.ts +1 -1
  127. package/src/ParserStream.ts +40 -19
  128. package/src/Syntax.ts +60 -36
  129. package/src/Text.ts +16 -15
  130. package/src/Token.ts +138 -29
  131. package/src/internal/compiler/closure.ts +362 -135
  132. package/src/internal/compiler/entries.ts +4 -2
  133. package/src/internal/compiler/first.ts +10 -0
  134. package/src/internal/compiler/graph.ts +68 -0
  135. package/src/internal/compiler/jit.ts +284 -154
  136. package/src/internal/compiler/registry.ts +10 -2
  137. package/src/internal/compiler/search.ts +68 -0
  138. package/src/internal/compiler/sets.ts +7 -3
  139. package/src/internal/compiler/wasm.ts +3203 -0
  140. package/src/internal/compiler/wasmAsm.ts +484 -0
  141. package/src/internal/entries.ts +10 -4
  142. package/src/internal/node.ts +54 -4
  143. package/src/internal/numbers.ts +3 -1
  144. package/src/internal/run.ts +71 -12
  145. package/src/internal/stack.ts +8 -1
  146. package/src/internal/syntax.ts +39 -18
  147. package/src/internal/text.ts +97 -9
  148. package/src/internal/tokens.ts +96 -14
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 =
@@ -288,6 +336,27 @@ export const f64: (endian: Endianness) => Parser<number, never, never, Uint8Arra
288
336
 
289
337
  const toCount = (n: number | bigint): number => (Predicate.isBigInt(n) ? Number(n) : n)
290
338
 
339
+ /** How many counts {@link byCount} keeps the parsers of. */
340
+ const CountCache = 256
341
+
342
+ /**
343
+ * `build`, keeping the parsers it built for the first {@link CountCache}
344
+ * counts seen: length-prefixed data repeats a few lengths, and building a
345
+ * parser per value cost several times the parse.
346
+ */
347
+ const byCount = <A>(build: (n: number) => A): ((n: number | bigint) => A) => {
348
+ const built = new Map<number, A>()
349
+ return (n) => {
350
+ const count = toCount(n)
351
+ let a = built.get(count)
352
+ if (a === undefined) {
353
+ a = build(count)
354
+ if (built.size < CountCache) built.set(count, a)
355
+ }
356
+ return a
357
+ }
358
+ }
359
+
291
360
  /**
292
361
  * Parses a length with `count`, then takes that many tokens (winnow's
293
362
  * `length_take`, nom's `length_data`).
@@ -298,12 +367,19 @@ const toCount = (n: number | bigint): number => (Predicate.isBigInt(n) ? Number(
298
367
  export const lengthTake = <E, R, I extends Input = Uint8Array>(
299
368
  count: Parser<number | bigint, E, R, I>
300
369
  ): 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")
370
+ const take = byCount((n) => Token.take<I>(n).step)
371
+ // The count compiles with the grammar: the step is built from its (see `Parser.combinator`)
372
+ return P.combinator(
373
+ [count],
374
+ ([countStep]) =>
375
+ (state: State<I>) => {
376
+ // SAFETY: the count parser's step, interpreted or compiled, returns its output (a number or bigint) or `Fail`
377
+ const n = countStep(state) as number | bigint | Fail
378
+ if (n === Fail) return Fail
379
+ return take(n)(state)
380
+ },
381
+ "lengthTake"
382
+ )
307
383
  }
308
384
 
309
385
  /**
@@ -317,7 +393,10 @@ export const lengthRepeat = <E, R, I extends Input, A, E2, R2, I2 extends Input>
317
393
  count: Parser<number | bigint, E, R, I>,
318
394
  parser: Parser<A, E2, R2, I2>
319
395
  ): 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>)
396
+ P.flatMap(
397
+ count,
398
+ byCount((n) => P.repeat(parser, n))
399
+ )
321
400
 
322
401
  /**
323
402
  * Parses a length with `count`, takes that many tokens, and applies
@@ -331,4 +410,5 @@ export const lengthAndThen = <E, R, I extends Input, A, E2, R2>(
331
410
  count: Parser<number | bigint, E, R, I>,
332
411
  parser: Parser<A, E2, R2, Slice<I> & Input>
333
412
  ): Parser<A, E | E2, R | R2, I> =>
413
+ // SAFETY: a slice of an input is itself an input (a string, a `Uint8Array` or an array), so `Slice<I>` is an `Input`
334
414
  P.mapParser(lengthTake(count) as Parser<Slice<I> & Input, E, R, I>, P.complete(parser))
package/src/Bits.ts CHANGED
@@ -93,7 +93,7 @@ const inUnits = <A>(state: State<any>, factor: number, step: (state: State<any>)
93
93
  * @category combinators
94
94
  */
95
95
  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
96
+ const step = self.step
97
97
  return make((state) => inUnits(state, 8, step), "bits")
98
98
  }
99
99
 
@@ -105,18 +105,24 @@ export const bits = <A, E, R>(self: Parser<A, E, R, BitInput>): Parser<A, E, R,
105
105
  * @category combinators
106
106
  */
107
107
  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
108
+ const step = self.step
109
109
  return make((state: State<any>) => inUnits(state, 1 / 8, step), "bytes")
110
110
  }
111
111
 
112
112
  const readBits = (state: State<any>, count: number): number => {
113
+ // SAFETY: bit-level parsers run inside `bits`, on the `Uint8Array` it was given (`BitInput` only re-indexes it in bits)
113
114
  const input = state.input as Uint8Array
114
115
  let pos = state.pos
115
116
  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++
117
+ let left = count
118
+ // As many bits as the current byte holds at once, not one at a time
119
+ while (left > 0) {
120
+ const avail = 8 - (pos & 7)
121
+ const n = left < avail ? left : avail
122
+ // Multiplying, not shifting: the value can have up to 53 bits
123
+ value = value * (1 << n) + ((input[pos >>> 3] >>> (avail - n)) & ((1 << n) - 1))
124
+ pos += n
125
+ left -= n
120
126
  }
121
127
  state.pos = pos
122
128
  return value
package/src/Compiler.ts CHANGED
@@ -4,11 +4,15 @@
4
4
  * `Parser.parseSync`, `Parser.parsePeek`, `Parser.parsePartial` and
5
5
  * `Parser.parseEffect` faster, from any `offset`.
6
6
  *
7
- * There are two compilers, one function each, so a program bundles only the
8
- * one it uses, and a program that compiles nothing bundles neither:
7
+ * There are three compilers, one function each, so a program bundles only
8
+ * the ones it uses, and a program that compiles nothing bundles none:
9
9
  *
10
+ * - {@link wasm} scans the input in WebAssembly and builds the value with
11
+ * generated JavaScript. It is the fastest on strings and bytes, and uses
12
+ * the JIT for array input and streaming, and where WebAssembly or
13
+ * `new Function` is unavailable.
10
14
  * - {@link jit} generates code with `new Function` where it is allowed, and
11
- * otherwise uses the closure compiler. It is the fastest.
15
+ * otherwise uses the closure compiler.
12
16
  * - {@link closure} builds specialized closures only. It never generates
13
17
  * code, and a bundle that uses only it contains no `new Function`, so it
14
18
  * is the choice under a Content Security Policy, where even probing for
@@ -32,7 +36,9 @@
32
36
  * **Results and errors are unchanged.** Compiled code records no
33
37
  * diagnostics. When it fails, the parser runs again, interpreted, from the
34
38
  * same offset, so every `ParseError` (offset, expected, context, cause and
35
- * message) is exactly the one the interpreter reports.
39
+ * message) is exactly the one the interpreter reports. Running out of
40
+ * partial input is not a failure that needs it: compiled code reports
41
+ * `Incomplete` where the interpreter does, with the same `needed`.
36
42
  *
37
43
  * **Callbacks should be pure.** On an input that fails, the callbacks given
38
44
  * to `map`, `filter`, `tryMap`, `tap`, `fold`, ... may run twice, once
@@ -41,19 +47,57 @@
41
47
  * nor those of a branch that is certain to fail, and token-set predicates
42
48
  * may be called once per character code and cached.
43
49
  *
44
- * `Parser.parsePartial` and `ParserStream.parseAll` run a build that
45
- * handles partial input. `ParserStream.parseMany`, `Parser.parseRecoverable`
46
- * and runs with the `trace` option are always interpreted. Parsers that
50
+ * `Parser.parsePartial`, `ParserStream.parseAll` and
51
+ * `ParserStream.parseMany` run a build that handles partial input;
52
+ * `parseMany` runs it once per value and parses a value again, interpreted,
53
+ * only when it fails. `Parser.parseRecoverable`, `Syntax`
54
+ * parses and runs with the `trace` option are always interpreted. Parsers that
47
55
  * can't be described, such as `Parser.make` and `Parser.gen`, run
48
- * interpreted inside compiled code.
56
+ * interpreted inside compiled code; the parsers given to a
57
+ * `Parser.combinator` compile, and its step is built from theirs.
49
58
  *
50
59
  * @since 0.2.0
51
60
  */
52
- import type { Input } from "./Input.js"
53
- import type { Parser } from "./Parser.js"
61
+ import type { Fail, Input, State } from "./Input.js"
62
+ import type { Parser, RunOptions } from "./Parser.js"
54
63
  import { backend as closureBackend } from "./internal/compiler/closure.js"
55
64
  import { backend as jitBackend } from "./internal/compiler/jit.js"
56
65
  import { register } from "./internal/compiler/registry.js"
66
+ import { backend as wasmBackend } from "./internal/compiler/wasm.js"
67
+ import { prepare } from "./internal/run.js"
68
+
69
+ /**
70
+ * Compiles a parser with the WebAssembly compiler. Returns the same parser.
71
+ *
72
+ * The input is copied into WebAssembly memory and scanned there, by code
73
+ * generated from the grammar; a JavaScript function generated with
74
+ * `new Function` then builds the value from what the scan recorded, with
75
+ * the same callbacks and the same objects as the JIT. Parts of the grammar
76
+ * with no WebAssembly form (such as `Parser.make`, `flatMap`, or `filter`
77
+ * on a value) run their JIT-compiled code from inside the scan.
78
+ *
79
+ * It uses the JIT (see {@link jit}) for array input, for the streaming build
80
+ * of `Parser.parsePartial` and `ParserStream`, and where `WebAssembly` or
81
+ * `new Function` is unavailable: a Content Security Policy needs both
82
+ * `'wasm-unsafe-eval'` and `'unsafe-eval'` for it. Short strings with the
83
+ * same characters may be returned as one string, which no program can tell
84
+ * apart from two.
85
+ *
86
+ * @example
87
+ * ```ts
88
+ * import { Ascii, Compiler, Parser, Token } from "@thomasfosterau/effect-parse"
89
+ *
90
+ * const list = Compiler.wasm(Parser.separated(Ascii.decInt, Token.literal(",")))
91
+ * Parser.parse(list, "1,2,3") // Result.succeed([1, 2, 3])
92
+ * ```
93
+ *
94
+ * @since 0.5.0
95
+ * @category compilation
96
+ */
97
+ export const wasm = <A, E, R, I extends Input>(self: Parser<A, E, R, I>): Parser<A, E, R, I> => {
98
+ register(self, wasmBackend)
99
+ return self
100
+ }
57
101
 
58
102
  /**
59
103
  * Compiles a parser with the JIT: generated code where `new Function` is
@@ -96,3 +140,50 @@ export const closure = <A, E, R, I extends Input>(self: Parser<A, E, R, I>): Par
96
140
  register(self, closureBackend)
97
141
  return self
98
142
  }
143
+
144
+ /**
145
+ * Prepares a parser for running many times on states you own, as
146
+ * `Parser.run` does: for drivers that make one small run per token, such as
147
+ * a lexer over a buffer. `Parser.run` looks the parser's compiled form up
148
+ * on every run, which costs about a quarter of a small token's time; the
149
+ * function returned here looks it up once (again only when the input kind
150
+ * changes, or between complete and partial states) and calls the compiled
151
+ * code directly.
152
+ *
153
+ * It returns exactly what `Parser.run(self, state, options)` returns, and
154
+ * leaves the state as it would: the output with the cursor where the
155
+ * parser stopped, or `Fail` with the interpreter's failure (or, with
156
+ * `diagnostics: false`, only the mode). Compile the parser first with
157
+ * {@link jit} or {@link closure}; a parser that isn't compiled runs
158
+ * interpreted. The compiled form is looked up on the first run, so compile
159
+ * the parser before using the runner: compiling it again later doesn't
160
+ * reach a runner that has already run.
161
+ *
162
+ * @example
163
+ * ```ts
164
+ * import { Ascii, Compiler, Input, Parser, Token } from "@thomasfosterau/effect-parse"
165
+ *
166
+ * const token = Compiler.jit(Parser.preceded(Ascii.space0, Parser.alt(Ascii.alpha1, Token.oneOf("+-"))))
167
+ * const next = Compiler.runner(token)
168
+ *
169
+ * const tokens = (text: string): Array<string> => {
170
+ * const state = new Input.State(text)
171
+ * const out: Array<string> = []
172
+ * while (state.pos < state.end) {
173
+ * const t = next(state.reset(state.pos))
174
+ * if (t === Input.Fail) break
175
+ * out.push(t)
176
+ * }
177
+ * return out
178
+ * }
179
+ *
180
+ * tokens("a + b") // ["a", "+", "b"]
181
+ * ```
182
+ *
183
+ * @since 0.5.0
184
+ * @category running
185
+ */
186
+ export const runner = <A, E, R, I extends Input>(
187
+ self: Parser<A, E, R, I>,
188
+ options?: RunOptions
189
+ ): ((state: State<I>) => A | Fail) => prepare(self, self.step, options?.diagnostics !== false)