@thomasfosterau/effect-parse 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +550 -2
- package/dist/Ascii.d.ts +392 -0
- package/dist/Ascii.d.ts.map +1 -0
- package/dist/Ascii.js +610 -0
- package/dist/Ascii.js.map +1 -0
- package/dist/Binary.d.ts +153 -0
- package/dist/Binary.d.ts.map +1 -0
- package/dist/Binary.js +171 -0
- package/dist/Binary.js.map +1 -0
- package/dist/Bits.d.ts +60 -0
- package/dist/Bits.d.ts.map +1 -0
- package/dist/Bits.js +144 -0
- package/dist/Bits.js.map +1 -0
- package/dist/Encoding.d.ts +116 -0
- package/dist/Encoding.d.ts.map +1 -0
- package/dist/Encoding.js +85 -0
- package/dist/Encoding.js.map +1 -0
- package/dist/Expression.d.ts +124 -0
- package/dist/Expression.d.ts.map +1 -0
- package/dist/Expression.js +139 -0
- package/dist/Expression.js.map +1 -0
- package/dist/Input.d.ts +301 -0
- package/dist/Input.d.ts.map +1 -0
- package/dist/Input.js +312 -0
- package/dist/Input.js.map +1 -0
- package/dist/Nom.d.ts +518 -0
- package/dist/Nom.d.ts.map +1 -0
- package/dist/Nom.js +519 -0
- package/dist/Nom.js.map +1 -0
- package/dist/ParseError.d.ts +120 -0
- package/dist/ParseError.d.ts.map +1 -0
- package/dist/ParseError.js +301 -0
- package/dist/ParseError.js.map +1 -0
- package/dist/Parser.d.ts +1312 -0
- package/dist/Parser.d.ts.map +1 -0
- package/dist/Parser.js +1936 -0
- package/dist/Parser.js.map +1 -0
- package/dist/ParserSchema.d.ts +31 -0
- package/dist/ParserSchema.d.ts.map +1 -0
- package/dist/ParserSchema.js +56 -0
- package/dist/ParserSchema.js.map +1 -0
- package/dist/ParserStream.d.ts +127 -0
- package/dist/ParserStream.d.ts.map +1 -0
- package/dist/ParserStream.js +270 -0
- package/dist/ParserStream.js.map +1 -0
- package/dist/Text.d.ts +330 -0
- package/dist/Text.d.ts.map +1 -0
- package/dist/Text.js +529 -0
- package/dist/Text.js.map +1 -0
- package/dist/Token.d.ts +225 -0
- package/dist/Token.d.ts.map +1 -0
- package/dist/Token.js +294 -0
- package/dist/Token.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/compiler/closure.d.ts +25 -0
- package/dist/internal/compiler/closure.d.ts.map +1 -0
- package/dist/internal/compiler/closure.js +1668 -0
- package/dist/internal/compiler/closure.js.map +1 -0
- package/dist/internal/compiler/entries.d.ts +19 -0
- package/dist/internal/compiler/entries.d.ts.map +1 -0
- package/dist/internal/compiler/entries.js +32 -0
- package/dist/internal/compiler/entries.js.map +1 -0
- package/dist/internal/compiler/first.d.ts +33 -0
- package/dist/internal/compiler/first.d.ts.map +1 -0
- package/dist/internal/compiler/first.js +235 -0
- package/dist/internal/compiler/first.js.map +1 -0
- package/dist/internal/compiler/jit.d.ts +20 -0
- package/dist/internal/compiler/jit.d.ts.map +1 -0
- package/dist/internal/compiler/jit.js +1030 -0
- package/dist/internal/compiler/jit.js.map +1 -0
- package/dist/internal/compiler/registry.d.ts +34 -0
- package/dist/internal/compiler/registry.d.ts.map +1 -0
- package/dist/internal/compiler/registry.js +46 -0
- package/dist/internal/compiler/registry.js.map +1 -0
- package/dist/internal/compiler/sets.d.ts +29 -0
- package/dist/internal/compiler/sets.d.ts.map +1 -0
- package/dist/internal/compiler/sets.js +85 -0
- package/dist/internal/compiler/sets.js.map +1 -0
- package/dist/internal/entries.d.ts +27 -0
- package/dist/internal/entries.d.ts.map +1 -0
- package/dist/internal/entries.js +42 -0
- package/dist/internal/entries.js.map +1 -0
- package/dist/internal/node.d.ts +251 -0
- package/dist/internal/node.d.ts.map +1 -0
- package/dist/internal/node.js +73 -0
- package/dist/internal/node.js.map +1 -0
- package/dist/internal/numbers.d.ts +41 -0
- package/dist/internal/numbers.d.ts.map +1 -0
- package/dist/internal/numbers.js +302 -0
- package/dist/internal/numbers.js.map +1 -0
- package/dist/internal/resume.d.ts +39 -0
- package/dist/internal/resume.d.ts.map +1 -0
- package/dist/internal/resume.js +37 -0
- package/dist/internal/resume.js.map +1 -0
- package/dist/internal/text.d.ts +72 -0
- package/dist/internal/text.d.ts.map +1 -0
- package/dist/internal/text.js +391 -0
- package/dist/internal/text.js.map +1 -0
- package/dist/internal/tokens.d.ts +93 -0
- package/dist/internal/tokens.d.ts.map +1 -0
- package/dist/internal/tokens.js +400 -0
- package/dist/internal/tokens.js.map +1 -0
- package/package.json +115 -3
- package/src/Ascii.ts +741 -0
- package/src/Binary.ts +228 -0
- package/src/Bits.ts +192 -0
- package/src/Encoding.ts +128 -0
- package/src/Expression.ts +245 -0
- package/src/Input.ts +429 -0
- package/src/Nom.ts +642 -0
- package/src/ParseError.ts +378 -0
- package/src/Parser.ts +2763 -0
- package/src/ParserSchema.ts +87 -0
- package/src/ParserStream.ts +390 -0
- package/src/Text.ts +757 -0
- package/src/Token.ts +416 -0
- package/src/index.ts +16 -0
- package/src/internal/compiler/closure.ts +1583 -0
- package/src/internal/compiler/entries.ts +36 -0
- package/src/internal/compiler/first.ts +252 -0
- package/src/internal/compiler/jit.ts +1091 -0
- package/src/internal/compiler/registry.ts +79 -0
- package/src/internal/compiler/sets.ts +100 -0
- package/src/internal/entries.ts +51 -0
- package/src/internal/node.ts +207 -0
- package/src/internal/numbers.ts +291 -0
- package/src/internal/resume.ts +55 -0
- package/src/internal/text.ts +411 -0
- 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")
|
package/src/Encoding.ts
ADDED
|
@@ -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
|
+
}
|