@thomasfosterau/effect-parse 0.0.0-stage → 0.0.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 (133) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +550 -2
  3. package/dist/Ascii.d.ts +392 -0
  4. package/dist/Ascii.d.ts.map +1 -0
  5. package/dist/Ascii.js +610 -0
  6. package/dist/Ascii.js.map +1 -0
  7. package/dist/Binary.d.ts +153 -0
  8. package/dist/Binary.d.ts.map +1 -0
  9. package/dist/Binary.js +171 -0
  10. package/dist/Binary.js.map +1 -0
  11. package/dist/Bits.d.ts +60 -0
  12. package/dist/Bits.d.ts.map +1 -0
  13. package/dist/Bits.js +144 -0
  14. package/dist/Bits.js.map +1 -0
  15. package/dist/Encoding.d.ts +116 -0
  16. package/dist/Encoding.d.ts.map +1 -0
  17. package/dist/Encoding.js +85 -0
  18. package/dist/Encoding.js.map +1 -0
  19. package/dist/Expression.d.ts +124 -0
  20. package/dist/Expression.d.ts.map +1 -0
  21. package/dist/Expression.js +139 -0
  22. package/dist/Expression.js.map +1 -0
  23. package/dist/Input.d.ts +301 -0
  24. package/dist/Input.d.ts.map +1 -0
  25. package/dist/Input.js +312 -0
  26. package/dist/Input.js.map +1 -0
  27. package/dist/Nom.d.ts +518 -0
  28. package/dist/Nom.d.ts.map +1 -0
  29. package/dist/Nom.js +519 -0
  30. package/dist/Nom.js.map +1 -0
  31. package/dist/ParseError.d.ts +120 -0
  32. package/dist/ParseError.d.ts.map +1 -0
  33. package/dist/ParseError.js +301 -0
  34. package/dist/ParseError.js.map +1 -0
  35. package/dist/Parser.d.ts +1312 -0
  36. package/dist/Parser.d.ts.map +1 -0
  37. package/dist/Parser.js +1936 -0
  38. package/dist/Parser.js.map +1 -0
  39. package/dist/ParserSchema.d.ts +31 -0
  40. package/dist/ParserSchema.d.ts.map +1 -0
  41. package/dist/ParserSchema.js +56 -0
  42. package/dist/ParserSchema.js.map +1 -0
  43. package/dist/ParserStream.d.ts +127 -0
  44. package/dist/ParserStream.d.ts.map +1 -0
  45. package/dist/ParserStream.js +270 -0
  46. package/dist/ParserStream.js.map +1 -0
  47. package/dist/Text.d.ts +330 -0
  48. package/dist/Text.d.ts.map +1 -0
  49. package/dist/Text.js +529 -0
  50. package/dist/Text.js.map +1 -0
  51. package/dist/Token.d.ts +225 -0
  52. package/dist/Token.d.ts.map +1 -0
  53. package/dist/Token.js +294 -0
  54. package/dist/Token.js.map +1 -0
  55. package/dist/index.d.ts +17 -0
  56. package/dist/index.d.ts.map +1 -0
  57. package/dist/index.js +17 -0
  58. package/dist/index.js.map +1 -0
  59. package/dist/internal/compiler/closure.d.ts +25 -0
  60. package/dist/internal/compiler/closure.d.ts.map +1 -0
  61. package/dist/internal/compiler/closure.js +1668 -0
  62. package/dist/internal/compiler/closure.js.map +1 -0
  63. package/dist/internal/compiler/entries.d.ts +19 -0
  64. package/dist/internal/compiler/entries.d.ts.map +1 -0
  65. package/dist/internal/compiler/entries.js +32 -0
  66. package/dist/internal/compiler/entries.js.map +1 -0
  67. package/dist/internal/compiler/first.d.ts +33 -0
  68. package/dist/internal/compiler/first.d.ts.map +1 -0
  69. package/dist/internal/compiler/first.js +235 -0
  70. package/dist/internal/compiler/first.js.map +1 -0
  71. package/dist/internal/compiler/jit.d.ts +20 -0
  72. package/dist/internal/compiler/jit.d.ts.map +1 -0
  73. package/dist/internal/compiler/jit.js +1030 -0
  74. package/dist/internal/compiler/jit.js.map +1 -0
  75. package/dist/internal/compiler/registry.d.ts +34 -0
  76. package/dist/internal/compiler/registry.d.ts.map +1 -0
  77. package/dist/internal/compiler/registry.js +46 -0
  78. package/dist/internal/compiler/registry.js.map +1 -0
  79. package/dist/internal/compiler/sets.d.ts +29 -0
  80. package/dist/internal/compiler/sets.d.ts.map +1 -0
  81. package/dist/internal/compiler/sets.js +85 -0
  82. package/dist/internal/compiler/sets.js.map +1 -0
  83. package/dist/internal/entries.d.ts +27 -0
  84. package/dist/internal/entries.d.ts.map +1 -0
  85. package/dist/internal/entries.js +42 -0
  86. package/dist/internal/entries.js.map +1 -0
  87. package/dist/internal/node.d.ts +251 -0
  88. package/dist/internal/node.d.ts.map +1 -0
  89. package/dist/internal/node.js +73 -0
  90. package/dist/internal/node.js.map +1 -0
  91. package/dist/internal/numbers.d.ts +41 -0
  92. package/dist/internal/numbers.d.ts.map +1 -0
  93. package/dist/internal/numbers.js +302 -0
  94. package/dist/internal/numbers.js.map +1 -0
  95. package/dist/internal/resume.d.ts +39 -0
  96. package/dist/internal/resume.d.ts.map +1 -0
  97. package/dist/internal/resume.js +37 -0
  98. package/dist/internal/resume.js.map +1 -0
  99. package/dist/internal/text.d.ts +72 -0
  100. package/dist/internal/text.d.ts.map +1 -0
  101. package/dist/internal/text.js +391 -0
  102. package/dist/internal/text.js.map +1 -0
  103. package/dist/internal/tokens.d.ts +93 -0
  104. package/dist/internal/tokens.d.ts.map +1 -0
  105. package/dist/internal/tokens.js +400 -0
  106. package/dist/internal/tokens.js.map +1 -0
  107. package/package.json +115 -3
  108. package/src/Ascii.ts +741 -0
  109. package/src/Binary.ts +228 -0
  110. package/src/Bits.ts +192 -0
  111. package/src/Encoding.ts +128 -0
  112. package/src/Expression.ts +245 -0
  113. package/src/Input.ts +429 -0
  114. package/src/Nom.ts +642 -0
  115. package/src/ParseError.ts +378 -0
  116. package/src/Parser.ts +2763 -0
  117. package/src/ParserSchema.ts +87 -0
  118. package/src/ParserStream.ts +390 -0
  119. package/src/Text.ts +757 -0
  120. package/src/Token.ts +416 -0
  121. package/src/index.ts +16 -0
  122. package/src/internal/compiler/closure.ts +1583 -0
  123. package/src/internal/compiler/entries.ts +36 -0
  124. package/src/internal/compiler/first.ts +252 -0
  125. package/src/internal/compiler/jit.ts +1091 -0
  126. package/src/internal/compiler/registry.ts +79 -0
  127. package/src/internal/compiler/sets.ts +100 -0
  128. package/src/internal/entries.ts +51 -0
  129. package/src/internal/node.ts +207 -0
  130. package/src/internal/numbers.ts +291 -0
  131. package/src/internal/resume.ts +55 -0
  132. package/src/internal/text.ts +411 -0
  133. package/src/internal/tokens.ts +409 -0
package/src/Binary.ts ADDED
@@ -0,0 +1,228 @@
1
+ /**
2
+ * Parsers for binary formats: fixed-width integers and floats in either
3
+ * byte order, and length-prefixed data (winnow's `binary` module, nom's
4
+ * `number` and `multi::length_*` parsers).
5
+ *
6
+ * Integers up to 32 bits are returned as `number`, wider integers as
7
+ * `bigint`.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * import { Binary, Parser } from "@thomasfosterau/effect-parse"
12
+ *
13
+ * // A length-prefixed (u16 big-endian) payload
14
+ * const frame = Binary.lengthTake(Binary.beU16)
15
+ * Parser.parse(frame, new Uint8Array([0, 2, 0xca, 0xfe])) // Uint8Array [0xca, 0xfe]
16
+ * ```
17
+ *
18
+ * @since 0.1.0
19
+ */
20
+ import type { Input, Slice, State } from "./Input.js"
21
+ import { Fail } from "./Input.js"
22
+ import type { Parser } from "./Parser.js"
23
+ import * as P from "./Parser.js"
24
+ import * as Token from "./Token.js"
25
+
26
+ export {
27
+ /**
28
+ * Runs a bit-level parser on byte input. See the `Bits` module.
29
+ *
30
+ * @since 0.1.0
31
+ * @category bits
32
+ */
33
+ bits
34
+ } from "./Bits.js"
35
+
36
+ /**
37
+ * A byte order. `"native"` is the byte order of the current platform.
38
+ *
39
+ * @since 0.1.0
40
+ * @category models
41
+ */
42
+ export type Endianness = "big" | "little" | "native"
43
+
44
+ const nativeLittle = new Uint8Array(new Uint16Array([1]).buffer)[0] === 1
45
+
46
+ const fixed = <A>(size: number, name: string, read: (view: DataView, offset: number) => A): Parser<A, never, never, Uint8Array> => {
47
+ const expected = [`${size} byte${size === 1 ? "" : "s"}`]
48
+ return P.make((state: State<Uint8Array>) => {
49
+ const pos = state.pos
50
+ const avail = state.end - pos
51
+ if (avail < size) return state.eoi("Token", expected, size - avail)
52
+ const bytes = state.input
53
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength)
54
+ state.pos = pos + size
55
+ return read(view, pos)
56
+ }, name)
57
+ }
58
+
59
+ const readU24 = (little: boolean) => (v: DataView, o: number) =>
60
+ little
61
+ ? v.getUint8(o) | (v.getUint8(o + 1) << 8) | (v.getUint8(o + 2) << 16)
62
+ : (v.getUint8(o) << 16) | (v.getUint8(o + 1) << 8) | v.getUint8(o + 2)
63
+
64
+ const readI24 = (little: boolean) => {
65
+ const u = readU24(little)
66
+ return (v: DataView, o: number) => {
67
+ const x = u(v, o)
68
+ return x & 0x800000 ? x - 0x1000000 : x
69
+ }
70
+ }
71
+
72
+ const readU128 = (little: boolean) => (v: DataView, o: number) => {
73
+ const a = v.getBigUint64(o, little)
74
+ const b = v.getBigUint64(o + 8, little)
75
+ return little ? (b << 64n) | a : (a << 64n) | b
76
+ }
77
+
78
+ const readI128 = (little: boolean) => {
79
+ const u = readU128(little)
80
+ return (v: DataView, o: number) => BigInt.asIntN(128, u(v, o))
81
+ }
82
+
83
+ /**
84
+ * An unsigned byte.
85
+ *
86
+ * @since 0.1.0
87
+ * @category integers
88
+ */
89
+ export const u8: Parser<number, never, never, Uint8Array> = fixed(1, "u8", (v, o) => v.getUint8(o))
90
+
91
+ /**
92
+ * A signed byte.
93
+ *
94
+ * @since 0.1.0
95
+ * @category integers
96
+ */
97
+ export const i8: Parser<number, never, never, Uint8Array> = fixed(1, "i8", (v, o) => v.getInt8(o))
98
+
99
+ /** @since 0.1.0 @category integers */
100
+ export const beU16: Parser<number, never, never, Uint8Array> = fixed(2, "beU16", (v, o) => v.getUint16(o, false))
101
+ /** @since 0.1.0 @category integers */
102
+ export const leU16: Parser<number, never, never, Uint8Array> = fixed(2, "leU16", (v, o) => v.getUint16(o, true))
103
+ /** @since 0.1.0 @category integers */
104
+ export const beI16: Parser<number, never, never, Uint8Array> = fixed(2, "beI16", (v, o) => v.getInt16(o, false))
105
+ /** @since 0.1.0 @category integers */
106
+ export const leI16: Parser<number, never, never, Uint8Array> = fixed(2, "leI16", (v, o) => v.getInt16(o, true))
107
+ /** @since 0.1.0 @category integers */
108
+ export const beU24: Parser<number, never, never, Uint8Array> = fixed(3, "beU24", readU24(false))
109
+ /** @since 0.1.0 @category integers */
110
+ export const leU24: Parser<number, never, never, Uint8Array> = fixed(3, "leU24", readU24(true))
111
+ /** @since 0.1.0 @category integers */
112
+ export const beI24: Parser<number, never, never, Uint8Array> = fixed(3, "beI24", readI24(false))
113
+ /** @since 0.1.0 @category integers */
114
+ export const leI24: Parser<number, never, never, Uint8Array> = fixed(3, "leI24", readI24(true))
115
+ /** @since 0.1.0 @category integers */
116
+ export const beU32: Parser<number, never, never, Uint8Array> = fixed(4, "beU32", (v, o) => v.getUint32(o, false))
117
+ /** @since 0.1.0 @category integers */
118
+ export const leU32: Parser<number, never, never, Uint8Array> = fixed(4, "leU32", (v, o) => v.getUint32(o, true))
119
+ /** @since 0.1.0 @category integers */
120
+ export const beI32: Parser<number, never, never, Uint8Array> = fixed(4, "beI32", (v, o) => v.getInt32(o, false))
121
+ /** @since 0.1.0 @category integers */
122
+ export const leI32: Parser<number, never, never, Uint8Array> = fixed(4, "leI32", (v, o) => v.getInt32(o, true))
123
+ /** @since 0.1.0 @category integers */
124
+ export const beU64: Parser<bigint, never, never, Uint8Array> = fixed(8, "beU64", (v, o) => v.getBigUint64(o, false))
125
+ /** @since 0.1.0 @category integers */
126
+ export const leU64: Parser<bigint, never, never, Uint8Array> = fixed(8, "leU64", (v, o) => v.getBigUint64(o, true))
127
+ /** @since 0.1.0 @category integers */
128
+ export const beI64: Parser<bigint, never, never, Uint8Array> = fixed(8, "beI64", (v, o) => v.getBigInt64(o, false))
129
+ /** @since 0.1.0 @category integers */
130
+ export const leI64: Parser<bigint, never, never, Uint8Array> = fixed(8, "leI64", (v, o) => v.getBigInt64(o, true))
131
+ /** @since 0.1.0 @category integers */
132
+ export const beU128: Parser<bigint, never, never, Uint8Array> = fixed(16, "beU128", readU128(false))
133
+ /** @since 0.1.0 @category integers */
134
+ export const leU128: Parser<bigint, never, never, Uint8Array> = fixed(16, "leU128", readU128(true))
135
+ /** @since 0.1.0 @category integers */
136
+ export const beI128: Parser<bigint, never, never, Uint8Array> = fixed(16, "beI128", readI128(false))
137
+ /** @since 0.1.0 @category integers */
138
+ export const leI128: Parser<bigint, never, never, Uint8Array> = fixed(16, "leI128", readI128(true))
139
+
140
+ /** @since 0.1.0 @category floats */
141
+ export const beF32: Parser<number, never, never, Uint8Array> = fixed(4, "beF32", (v, o) => v.getFloat32(o, false))
142
+ /** @since 0.1.0 @category floats */
143
+ export const leF32: Parser<number, never, never, Uint8Array> = fixed(4, "leF32", (v, o) => v.getFloat32(o, true))
144
+ /** @since 0.1.0 @category floats */
145
+ export const beF64: Parser<number, never, never, Uint8Array> = fixed(8, "beF64", (v, o) => v.getFloat64(o, false))
146
+ /** @since 0.1.0 @category floats */
147
+ export const leF64: Parser<number, never, never, Uint8Array> = fixed(8, "leF64", (v, o) => v.getFloat64(o, true))
148
+
149
+ const byEndian = <A>(big: A, little: A) => (endian: Endianness): A =>
150
+ endian === "big" ? big : endian === "little" ? little : nativeLittle ? little : big
151
+
152
+ /**
153
+ * An unsigned 16-bit integer in the given byte order (winnow's `u16`).
154
+ *
155
+ * @since 0.1.0
156
+ * @category integers
157
+ */
158
+ export const u16: (endian: Endianness) => Parser<number, never, never, Uint8Array> = byEndian(beU16, leU16)
159
+ /** @since 0.1.0 @category integers */
160
+ export const i16: (endian: Endianness) => Parser<number, never, never, Uint8Array> = byEndian(beI16, leI16)
161
+ /** @since 0.1.0 @category integers */
162
+ export const u24: (endian: Endianness) => Parser<number, never, never, Uint8Array> = byEndian(beU24, leU24)
163
+ /** @since 0.1.0 @category integers */
164
+ export const i24: (endian: Endianness) => Parser<number, never, never, Uint8Array> = byEndian(beI24, leI24)
165
+ /** @since 0.1.0 @category integers */
166
+ export const u32: (endian: Endianness) => Parser<number, never, never, Uint8Array> = byEndian(beU32, leU32)
167
+ /** @since 0.1.0 @category integers */
168
+ export const i32: (endian: Endianness) => Parser<number, never, never, Uint8Array> = byEndian(beI32, leI32)
169
+ /** @since 0.1.0 @category integers */
170
+ export const u64: (endian: Endianness) => Parser<bigint, never, never, Uint8Array> = byEndian(beU64, leU64)
171
+ /** @since 0.1.0 @category integers */
172
+ export const i64: (endian: Endianness) => Parser<bigint, never, never, Uint8Array> = byEndian(beI64, leI64)
173
+ /** @since 0.1.0 @category integers */
174
+ export const u128: (endian: Endianness) => Parser<bigint, never, never, Uint8Array> = byEndian(beU128, leU128)
175
+ /** @since 0.1.0 @category integers */
176
+ export const i128: (endian: Endianness) => Parser<bigint, never, never, Uint8Array> = byEndian(beI128, leI128)
177
+ /** @since 0.1.0 @category floats */
178
+ export const f32: (endian: Endianness) => Parser<number, never, never, Uint8Array> = byEndian(beF32, leF32)
179
+ /** @since 0.1.0 @category floats */
180
+ export const f64: (endian: Endianness) => Parser<number, never, never, Uint8Array> = byEndian(beF64, leF64)
181
+
182
+
183
+ const toCount = (n: number | bigint): number => typeof n === "bigint" ? Number(n) : n
184
+
185
+ /**
186
+ * Parses a length with `count`, then takes that many tokens (winnow's
187
+ * `length_take`, nom's `length_data`).
188
+ *
189
+ * @since 0.1.0
190
+ * @category length-prefixed
191
+ */
192
+ export const lengthTake = <E, R, I extends Input = Uint8Array>(
193
+ count: Parser<number | bigint, E, R, I>
194
+ ): Parser<Slice<I>, E, R, I> => {
195
+ const countStep = count.step
196
+ return P.make((state: State<I>) => {
197
+ const n = countStep(state)
198
+ if (n === Fail) return Fail
199
+ return (Token.take<I>(toCount(n)).step as any)(state)
200
+ }, "lengthTake")
201
+ }
202
+
203
+ /**
204
+ * Parses a length with `count`, then applies `parser` that many times
205
+ * (winnow's `length_repeat`, nom's `length_count`).
206
+ *
207
+ * @since 0.1.0
208
+ * @category length-prefixed
209
+ */
210
+ export const lengthRepeat = <E, R, I extends Input, A, E2, R2, I2 extends Input>(
211
+ count: Parser<number | bigint, E, R, I>,
212
+ parser: Parser<A, E2, R2, I2>
213
+ ): Parser<Array<A>, E | E2, R | R2, I & I2> =>
214
+ P.flatMap(count, (n) => P.repeat(parser, toCount(n)) as Parser<Array<A>, E2, R2, I2>)
215
+
216
+ /**
217
+ * Parses a length with `count`, takes that many tokens, and applies
218
+ * `parser` to them (winnow's `length_and_then`, nom's `length_value`).
219
+ * `parser` does not need to consume the whole slice.
220
+ *
221
+ * @since 0.1.0
222
+ * @category length-prefixed
223
+ */
224
+ export const lengthAndThen = <E, R, I extends Input, A, E2, R2>(
225
+ count: Parser<number | bigint, E, R, I>,
226
+ parser: Parser<A, E2, R2, Slice<I> & Input>
227
+ ): Parser<A, E | E2, R | R2, I> =>
228
+ P.mapParser(lengthTake(count) as Parser<Slice<I> & Input, E, R, I>, P.complete(parser))
package/src/Bits.ts ADDED
@@ -0,0 +1,192 @@
1
+ /**
2
+ * Bit-level parsing (winnow's `binary::bits`, nom's `bits` module).
3
+ *
4
+ * Bit parsers run inside {@link bits}, which switches a byte parser into
5
+ * bit mode. Bits are read most-significant first. When `bits` finishes, any
6
+ * remaining bits of a partly consumed byte are skipped.
7
+ *
8
+ * Inside `bits`, offsets count bits, so the general combinators (`alt`,
9
+ * `repeat`, `opt`, ...) backtrack at bit granularity.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * import { Binary, Bits, Parser } from "@thomasfosterau/effect-parse"
14
+ *
15
+ * // An IPv4 header starts with a 4-bit version and a 4-bit header length
16
+ * const versionAndLength = Bits.bits(Parser.seq(Bits.take(4), Bits.take(4)))
17
+ *
18
+ * Parser.parse(versionAndLength, new Uint8Array([0x45])) // Result.succeed([4, 5])
19
+ * ```
20
+ *
21
+ * @since 0.1.0
22
+ */
23
+ import type { State } from "./Input.js"
24
+ import { Fail, Failure } from "./Input.js"
25
+ import type { Parser } from "./Parser.js"
26
+ import { make } from "./Parser.js"
27
+
28
+ declare const BitInputBrand: unique symbol
29
+
30
+ /**
31
+ * The input type of bit-level parsers. A `Parser<A, E, R, BitInput>` can
32
+ * only be run inside {@link bits}.
33
+ *
34
+ * @since 0.1.0
35
+ * @category models
36
+ */
37
+ export interface BitInput extends ReadonlyArray<never> {
38
+ readonly [BitInputBrand]: true
39
+ }
40
+
41
+ const scale = (f: Failure, factor: number): Failure =>
42
+ new Failure(
43
+ factor < 1 ? Math.floor(f.offset * factor) : f.offset * factor,
44
+ f.kind,
45
+ f.expected,
46
+ f.context,
47
+ f.hasCause,
48
+ f.cause,
49
+ f.message,
50
+ factor < 1 ? Math.ceil(f.needed * factor) : f.needed * factor
51
+ )
52
+
53
+ /**
54
+ * Runs `step` with offsets measured in different units (bits vs bytes),
55
+ * converting failure offsets back to the outer units afterwards.
56
+ */
57
+ const inUnits = <A>(state: State<any>, factor: number, step: (state: State<any>) => A | Fail): A | Fail => {
58
+ const end = state.end
59
+ const deepest = state.deepest
60
+ state.deepest = undefined
61
+ state.pos = factor > 1 ? state.pos * factor : Math.ceil(state.pos * factor)
62
+ state.end = end * factor
63
+ const a = step(state)
64
+ state.end = end
65
+ const inner = state.deepest
66
+ state.deepest = deepest
67
+ if (a === Fail) {
68
+ state.pos = factor > 1 ? Math.floor(state.pos / factor) : state.pos / factor
69
+ const f = state.failure
70
+ state.failure = scale(f, 1 / factor)
71
+ if (inner !== undefined) {
72
+ const d = inner === f ? state.failure : scale(inner, 1 / factor)
73
+ if (deepest === undefined || d.offset >= deepest.offset) state.deepest = d
74
+ }
75
+ return Fail
76
+ }
77
+ state.pos = factor > 1 ? Math.ceil(state.pos / factor) : state.pos / factor
78
+ if (inner !== undefined) {
79
+ const d = scale(inner, 1 / factor)
80
+ if (deepest === undefined || d.offset >= deepest.offset) state.deepest = d
81
+ }
82
+ return a
83
+ }
84
+
85
+ /**
86
+ * Runs a bit-level parser on byte input (winnow's and nom's `bits`). After
87
+ * success, the input advances to the next byte boundary.
88
+ *
89
+ * @since 0.1.0
90
+ * @category combinators
91
+ */
92
+ export const bits = <A, E, R>(self: Parser<A, E, R, BitInput>): Parser<A, E, R, Uint8Array> => {
93
+ const step = self.step as unknown as (state: State<any>) => A | Fail
94
+ return make((state) => inUnits(state, 8, step), "bits")
95
+ }
96
+
97
+ /**
98
+ * Runs a byte-level parser from within a bit-level parser (winnow's and
99
+ * nom's `bytes`). The partly consumed byte, if any, is skipped first.
100
+ *
101
+ * @since 0.1.0
102
+ * @category combinators
103
+ */
104
+ export const bytes = <A, E, R>(self: Parser<A, E, R, Uint8Array>): Parser<A, E, R, BitInput> => {
105
+ const step = self.step as (state: State<any>) => A | Fail
106
+ return make((state: State<any>) => inUnits(state, 1 / 8, step), "bytes")
107
+ }
108
+
109
+ const readBits = (state: State<any>, count: number): number => {
110
+ const input = state.input as Uint8Array
111
+ let pos = state.pos
112
+ let value = 0
113
+ for (let i = 0; i < count; i++) {
114
+ const byte = input[pos >>> 3]!
115
+ value = value * 2 + ((byte >>> (7 - (pos & 7))) & 1)
116
+ pos++
117
+ }
118
+ state.pos = pos
119
+ return value
120
+ }
121
+
122
+ /**
123
+ * Takes `count` bits (at most 53) as an unsigned number (winnow's and nom's
124
+ * bit-level `take`).
125
+ *
126
+ * @since 0.1.0
127
+ * @category parsers
128
+ */
129
+ export const take = (count: number): Parser<number, never, never, BitInput> => {
130
+ if (count > 53) throw new RangeError("Bits.take: use Bits.takeBig for more than 53 bits")
131
+ const expected = [`${count} bits`]
132
+ return make((state: State<any>) => {
133
+ const avail = state.end - state.pos
134
+ if (avail < count) return state.eoi("Bits", expected, count - avail)
135
+ return readBits(state, count)
136
+ }, "take")
137
+ }
138
+
139
+ /**
140
+ * Takes `count` bits as an unsigned `bigint`.
141
+ *
142
+ * @since 0.1.0
143
+ * @category parsers
144
+ */
145
+ export const takeBig = (count: number): Parser<bigint, never, never, BitInput> => {
146
+ const expected = [`${count} bits`]
147
+ return make((state: State<any>) => {
148
+ const avail = state.end - state.pos
149
+ if (avail < count) return state.eoi("Bits", expected, count - avail)
150
+ let value = 0n
151
+ let remaining = count
152
+ while (remaining > 0) {
153
+ const n = Math.min(remaining, 32)
154
+ value = (value << BigInt(n)) | BigInt(readBits(state, n))
155
+ remaining -= n
156
+ }
157
+ return value
158
+ }, "takeBig")
159
+ }
160
+
161
+ /**
162
+ * Matches `count` bits equal to `pattern` (winnow's `pattern`, nom's bit
163
+ * `tag`).
164
+ *
165
+ * @since 0.1.0
166
+ * @category parsers
167
+ */
168
+ export const pattern = (pattern: number, count: number): Parser<number, never, never, BitInput> => {
169
+ const expected = [`bit pattern ${pattern.toString(2).padStart(count, "0")}`]
170
+ return make((state: State<any>) => {
171
+ const start = state.pos
172
+ const avail = state.end - start
173
+ if (avail < count) return state.eoi("Bits", expected, count - avail)
174
+ const value = readBits(state, count)
175
+ if (value !== pattern) {
176
+ state.pos = start
177
+ return state.backtrack("Bits", expected)
178
+ }
179
+ return value
180
+ }, "pattern")
181
+ }
182
+
183
+ /**
184
+ * Takes one bit as a boolean (winnow's and nom's `bool`).
185
+ *
186
+ * @since 0.1.0
187
+ * @category parsers
188
+ */
189
+ export const bool: Parser<boolean, never, never, BitInput> = make((state: State<any>) => {
190
+ if (state.pos >= state.end) return state.eoi("Bits", ["1 bit"], 1)
191
+ return readBits(state, 1) === 1
192
+ }, "bool")
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Text encodings for the `Text` module.
3
+ *
4
+ * An {@link Encoding} tells the `Text` parsers how to read Unicode code
5
+ * points out of an input. JavaScript strings are read natively, without any
6
+ * conversion. Byte inputs are decoded as they are parsed, so offsets stay
7
+ * byte offsets, slices that are kept as bytes (`Parser.taken`) are
8
+ * zero-copy views, and partial input that ends in the middle of a character
9
+ * reports `Incomplete`.
10
+ *
11
+ * ```ts
12
+ * import { Encoding, Text } from "@thomasfosterau/effect-parse"
13
+ *
14
+ * Text.utf8.encoding === Encoding.utf8
15
+ * Text.make(Encoding.latin1) // the Text parsers for any Encoding, including your own
16
+ * ```
17
+ *
18
+ * @since 0.1.0
19
+ */
20
+ import * as Option from "effect/Option"
21
+ import * as internal from "./internal/text.js"
22
+
23
+ /**
24
+ * A text encoding: how to read Unicode code points out of an input. The
25
+ * `Text` module is generic over encodings, so one grammar can parse
26
+ * JavaScript strings, UTF-8 bytes, UTF-16 bytes and so on.
27
+ *
28
+ * Decoding is strict: malformed sequences (invalid UTF-8, unpaired UTF-16
29
+ * surrogates) are reported as invalid, never replaced.
30
+ *
31
+ * @since 0.1.0
32
+ * @category models
33
+ */
34
+ export interface Encoding<I extends string | Uint8Array = string | Uint8Array> {
35
+ /** The name of the encoding, e.g. `"UTF-8"`. Used in error messages. */
36
+ readonly name: string
37
+ /**
38
+ * The size of a code unit, in input units: 1 for strings, UTF-8 and
39
+ * Latin-1, 2 for UTF-16 bytes. Code points start at multiples of it.
40
+ */
41
+ readonly unit: number
42
+ /**
43
+ * Whether every input unit below `0x80` encodes the ASCII character with
44
+ * that value, and is never part of a longer sequence. Enables fast paths.
45
+ */
46
+ readonly asciiCompatible: boolean
47
+ /**
48
+ * Decodes the code point starting at `offset` (with `offset < end`).
49
+ * Returns the code point, `-1` if the input is malformed there, or `-2` if
50
+ * the input ends inside a sequence (more input could complete it).
51
+ */
52
+ readonly decode: (input: I, offset: number, end: number) => number
53
+ /** The number of input units that encode a code point. */
54
+ readonly width: (codePoint: number) => number
55
+ /** Encodes a string. Throws if the string can't be represented. */
56
+ readonly encode: (text: string) => I
57
+ /**
58
+ * Decodes the units `[start, end)` to a string. The parsers only call it
59
+ * on ranges they have checked are well formed.
60
+ */
61
+ readonly text: (input: I, start: number, end: number) => string
62
+ }
63
+
64
+ /**
65
+ * JavaScript strings, read as UTF-16 code units. Offsets are code-unit
66
+ * indices and slices are plain `String.prototype.slice`s. Unpaired
67
+ * surrogates are malformed.
68
+ *
69
+ * @since 0.1.0
70
+ * @category encodings
71
+ */
72
+ export const string: Encoding<string> = internal.string
73
+
74
+ /**
75
+ * UTF-8 bytes. Overlong encodings, surrogates and code points above
76
+ * U+10FFFF are malformed.
77
+ *
78
+ * @since 0.1.0
79
+ * @category encodings
80
+ */
81
+ export const utf8: Encoding<Uint8Array> = internal.utf8
82
+
83
+ /**
84
+ * UTF-16 bytes, little endian. Unpaired surrogates are malformed.
85
+ *
86
+ * @since 0.1.0
87
+ * @category encodings
88
+ */
89
+ export const utf16le: Encoding<Uint8Array> = internal.utf16le
90
+
91
+ /**
92
+ * UTF-16 bytes, big endian. Unpaired surrogates are malformed.
93
+ *
94
+ * @since 0.1.0
95
+ * @category encodings
96
+ */
97
+ export const utf16be: Encoding<Uint8Array> = internal.utf16be
98
+
99
+ /**
100
+ * ISO-8859-1 (Latin-1) bytes: each byte is the code point U+0000 to U+00FF
101
+ * of the same value.
102
+ *
103
+ * @since 0.1.0
104
+ * @category encodings
105
+ */
106
+ export const latin1: Encoding<Uint8Array> = internal.latin1
107
+
108
+ /**
109
+ * Detects the encoding of bytes from a byte order mark: UTF-8
110
+ * (`EF BB BF`), UTF-16LE (`FF FE`) or UTF-16BE (`FE FF`). The mark itself
111
+ * is not skipped; parse it with `Text.bom`.
112
+ *
113
+ * @example
114
+ * ```ts
115
+ * import { Encoding } from "@thomasfosterau/effect-parse"
116
+ *
117
+ * Encoding.sniff(new Uint8Array([0xff, 0xfe, 0x41, 0x00])) // Option.some(Encoding.utf16le)
118
+ * ```
119
+ *
120
+ * @since 0.1.0
121
+ * @category utils
122
+ */
123
+ export const sniff = (bytes: Uint8Array): Option.Option<Encoding<Uint8Array>> => {
124
+ if (bytes[0] === 0xef && bytes[1] === 0xbb && bytes[2] === 0xbf) return Option.some(utf8)
125
+ if (bytes[0] === 0xff && bytes[1] === 0xfe) return Option.some(utf16le)
126
+ if (bytes[0] === 0xfe && bytes[1] === 0xff) return Option.some(utf16be)
127
+ return Option.none()
128
+ }