@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.
- 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
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
import type { Encoding } from "../Encoding.js"
|
|
2
|
+
import type { Fail, State } from "../Input.js"
|
|
3
|
+
|
|
4
|
+
// -----------------------------------------------------------------------------
|
|
5
|
+
// Decoding results
|
|
6
|
+
// -----------------------------------------------------------------------------
|
|
7
|
+
|
|
8
|
+
/** `Encoding.decode`: the input is malformed. */
|
|
9
|
+
export const Invalid = -1
|
|
10
|
+
/** `Encoding.decode`: the input ends inside a sequence. */
|
|
11
|
+
export const Truncated = -2
|
|
12
|
+
/** `next`: the input has ended. */
|
|
13
|
+
export const End = -3
|
|
14
|
+
|
|
15
|
+
// -----------------------------------------------------------------------------
|
|
16
|
+
// Encodings
|
|
17
|
+
// -----------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Units below this value decode to themselves with width 1, so hot loops can
|
|
21
|
+
* skip `decode`. Defaults to 0x80 for ASCII compatible encodings.
|
|
22
|
+
*/
|
|
23
|
+
const FastLimit: unique symbol = Symbol.for("effect-parse/Encoding/FastLimit")
|
|
24
|
+
|
|
25
|
+
/** @internal */
|
|
26
|
+
export const fastLimit = (enc: Encoding<any>): number =>
|
|
27
|
+
(enc as any)[FastLimit] ?? (enc.asciiCompatible && enc.unit === 1 ? 0x80 : 0)
|
|
28
|
+
|
|
29
|
+
const asciiChars: ReadonlyArray<string> = Array.from({ length: 128 }, (_, i) => String.fromCharCode(i))
|
|
30
|
+
|
|
31
|
+
/** `String.fromCodePoint`, with the ASCII characters cached. */
|
|
32
|
+
export const charOf = (cp: number): string =>
|
|
33
|
+
cp < 128 ? asciiChars[cp]! : cp < 0x10000 ? String.fromCharCode(cp) : String.fromCodePoint(cp)
|
|
34
|
+
|
|
35
|
+
const decodeString = (s: string, i: number, end: number): number => {
|
|
36
|
+
const c = s.charCodeAt(i)
|
|
37
|
+
if ((c & 0xf800) !== 0xd800) return c
|
|
38
|
+
if (c <= 0xdbff) {
|
|
39
|
+
if (i + 1 >= end) return Truncated
|
|
40
|
+
const d = s.charCodeAt(i + 1)
|
|
41
|
+
if ((d & 0xfc00) === 0xdc00) return ((c - 0xd800) << 10) + (d - 0xdc00) + 0x10000
|
|
42
|
+
}
|
|
43
|
+
return Invalid
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* JavaScript strings: UTF-16 code units. Offsets are code-unit indices and
|
|
48
|
+
* slices are `String.prototype.slice`s, so no decoding is needed.
|
|
49
|
+
*/
|
|
50
|
+
export const string: Encoding<string> = {
|
|
51
|
+
name: "UTF-16",
|
|
52
|
+
unit: 1,
|
|
53
|
+
asciiCompatible: true,
|
|
54
|
+
decode: decodeString,
|
|
55
|
+
width: (cp) => cp > 0xffff ? 2 : 1,
|
|
56
|
+
encode: (text) => text,
|
|
57
|
+
text: (s, start, end) => s.slice(start, end),
|
|
58
|
+
[FastLimit]: 0xd800
|
|
59
|
+
} as Encoding<string>
|
|
60
|
+
|
|
61
|
+
const decodeUtf8 = (b: Uint8Array, i: number, end: number): number => {
|
|
62
|
+
const c = b[i]!
|
|
63
|
+
if (c < 0x80) return c
|
|
64
|
+
if (c < 0xc2) return Invalid
|
|
65
|
+
if (c < 0xe0) {
|
|
66
|
+
if (i + 1 >= end) return Truncated
|
|
67
|
+
const d = b[i + 1]!
|
|
68
|
+
return (d & 0xc0) === 0x80 ? ((c & 0x1f) << 6) | (d & 0x3f) : Invalid
|
|
69
|
+
}
|
|
70
|
+
if (c < 0xf0) {
|
|
71
|
+
if (i + 1 >= end) return Truncated
|
|
72
|
+
const d = b[i + 1]!
|
|
73
|
+
// Reject overlong encodings (E0 80..9F) and surrogates (ED A0..BF)
|
|
74
|
+
if (c === 0xe0 ? d < 0xa0 || d > 0xbf : c === 0xed ? d < 0x80 || d > 0x9f : (d & 0xc0) !== 0x80) return Invalid
|
|
75
|
+
if (i + 2 >= end) return Truncated
|
|
76
|
+
const e = b[i + 2]!
|
|
77
|
+
return (e & 0xc0) === 0x80 ? ((c & 0x0f) << 12) | ((d & 0x3f) << 6) | (e & 0x3f) : Invalid
|
|
78
|
+
}
|
|
79
|
+
if (c < 0xf5) {
|
|
80
|
+
if (i + 1 >= end) return Truncated
|
|
81
|
+
const d = b[i + 1]!
|
|
82
|
+
// Reject overlong encodings (F0 80..8F) and code points above U+10FFFF (F4 90..BF)
|
|
83
|
+
if (c === 0xf0 ? d < 0x90 || d > 0xbf : c === 0xf4 ? d < 0x80 || d > 0x8f : (d & 0xc0) !== 0x80) return Invalid
|
|
84
|
+
if (i + 2 >= end) return Truncated
|
|
85
|
+
const e = b[i + 2]!
|
|
86
|
+
if ((e & 0xc0) !== 0x80) return Invalid
|
|
87
|
+
if (i + 3 >= end) return Truncated
|
|
88
|
+
const f = b[i + 3]!
|
|
89
|
+
return (f & 0xc0) === 0x80 ? ((c & 0x07) << 18) | ((d & 0x3f) << 12) | ((e & 0x3f) << 6) | (f & 0x3f) : Invalid
|
|
90
|
+
}
|
|
91
|
+
return Invalid
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Building short strings by hand is several times faster than a
|
|
95
|
+
// `TextDecoder` call, which has a high fixed cost.
|
|
96
|
+
const SHORT = 24
|
|
97
|
+
|
|
98
|
+
const utf8Decoder = new TextDecoder("utf-8", { ignoreBOM: true })
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Decodes `b[start, end)`. Some platforms' `TextDecoder` rejects views of a
|
|
102
|
+
* `SharedArrayBuffer`, so those are copied first.
|
|
103
|
+
*/
|
|
104
|
+
const decodeRange = (decoder: TextDecoder, b: Uint8Array, start: number, end: number): string => {
|
|
105
|
+
const view = b.subarray(start, end)
|
|
106
|
+
return decoder.decode(b.buffer instanceof ArrayBuffer ? view : view.slice())
|
|
107
|
+
}
|
|
108
|
+
const utf8Encoder = new TextEncoder()
|
|
109
|
+
|
|
110
|
+
/** UTF-8 bytes. */
|
|
111
|
+
export const utf8: Encoding<Uint8Array> = {
|
|
112
|
+
name: "UTF-8",
|
|
113
|
+
unit: 1,
|
|
114
|
+
asciiCompatible: true,
|
|
115
|
+
decode: decodeUtf8,
|
|
116
|
+
width: (cp) => cp < 0x80 ? 1 : cp < 0x800 ? 2 : cp < 0x10000 ? 3 : 4,
|
|
117
|
+
encode: (text) => utf8Encoder.encode(text),
|
|
118
|
+
text: (b, start, end) => {
|
|
119
|
+
if (end - start > SHORT) return decodeRange(utf8Decoder, b, start, end)
|
|
120
|
+
let out = ""
|
|
121
|
+
for (let i = start; i < end;) {
|
|
122
|
+
const c = b[i]!
|
|
123
|
+
if (c < 0x80) {
|
|
124
|
+
out += asciiChars[c]
|
|
125
|
+
i++
|
|
126
|
+
} else {
|
|
127
|
+
const cp = decodeUtf8(b, i, end)
|
|
128
|
+
out += cp < 0x10000 ? String.fromCharCode(cp) : String.fromCodePoint(cp)
|
|
129
|
+
i += cp < 0x800 ? 2 : cp < 0x10000 ? 3 : 4
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return out
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const makeUtf16 = (littleEndian: boolean): Encoding<Uint8Array> => {
|
|
137
|
+
const lo = littleEndian ? 0 : 1
|
|
138
|
+
const hi = 1 - lo
|
|
139
|
+
let decoder: TextDecoder | undefined
|
|
140
|
+
return {
|
|
141
|
+
name: littleEndian ? "UTF-16LE" : "UTF-16BE",
|
|
142
|
+
unit: 2,
|
|
143
|
+
asciiCompatible: false,
|
|
144
|
+
decode: (b, i, end) => {
|
|
145
|
+
if (i + 1 >= end) return Truncated
|
|
146
|
+
const c = b[i + lo]! | (b[i + hi]! << 8)
|
|
147
|
+
if ((c & 0xf800) !== 0xd800) return c
|
|
148
|
+
if (c > 0xdbff) return Invalid
|
|
149
|
+
if (i + 3 >= end) return Truncated
|
|
150
|
+
const d = b[i + 2 + lo]! | (b[i + 2 + hi]! << 8)
|
|
151
|
+
return (d & 0xfc00) === 0xdc00 ? ((c - 0xd800) << 10) + (d - 0xdc00) + 0x10000 : Invalid
|
|
152
|
+
},
|
|
153
|
+
width: (cp) => cp > 0xffff ? 4 : 2,
|
|
154
|
+
encode: (text) => {
|
|
155
|
+
const out = new Uint8Array(text.length * 2)
|
|
156
|
+
for (let i = 0; i < text.length; i++) {
|
|
157
|
+
const c = text.charCodeAt(i)
|
|
158
|
+
out[2 * i + lo] = c & 0xff
|
|
159
|
+
out[2 * i + hi] = c >> 8
|
|
160
|
+
}
|
|
161
|
+
return out
|
|
162
|
+
},
|
|
163
|
+
text: (b, start, end) => {
|
|
164
|
+
if (end - start <= SHORT * 2) {
|
|
165
|
+
// The range is well formed, so surrogate pairs can be copied unit by unit
|
|
166
|
+
let out = ""
|
|
167
|
+
for (let i = start; i < end; i += 2) out += String.fromCharCode(b[i + lo]! | (b[i + hi]! << 8))
|
|
168
|
+
return out
|
|
169
|
+
}
|
|
170
|
+
decoder ??= new TextDecoder(littleEndian ? "utf-16le" : "utf-16be", { ignoreBOM: true })
|
|
171
|
+
return decodeRange(decoder, b, start, end)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** UTF-16 bytes, little endian. */
|
|
177
|
+
export const utf16le: Encoding<Uint8Array> = makeUtf16(true)
|
|
178
|
+
|
|
179
|
+
/** UTF-16 bytes, big endian. */
|
|
180
|
+
export const utf16be: Encoding<Uint8Array> = makeUtf16(false)
|
|
181
|
+
|
|
182
|
+
/** ISO-8859-1: every byte is the code point of the same value. */
|
|
183
|
+
export const latin1: Encoding<Uint8Array> = {
|
|
184
|
+
name: "ISO-8859-1",
|
|
185
|
+
unit: 1,
|
|
186
|
+
asciiCompatible: true,
|
|
187
|
+
decode: (b, i) => b[i]!,
|
|
188
|
+
width: () => 1,
|
|
189
|
+
encode: (text) => {
|
|
190
|
+
const out = new Uint8Array(text.length)
|
|
191
|
+
for (let i = 0; i < text.length; i++) {
|
|
192
|
+
const c = text.charCodeAt(i)
|
|
193
|
+
if (c > 0xff) throw new RangeError(`${JSON.stringify(text[i])} can't be encoded as ISO-8859-1`)
|
|
194
|
+
out[i] = c
|
|
195
|
+
}
|
|
196
|
+
return out
|
|
197
|
+
},
|
|
198
|
+
text: (b, start, end) => {
|
|
199
|
+
let out = ""
|
|
200
|
+
for (let i = start; i < end; i += 8192) {
|
|
201
|
+
out += String.fromCharCode.apply(null, b.subarray(i, Math.min(end, i + 8192)) as unknown as Array<number>)
|
|
202
|
+
}
|
|
203
|
+
return out
|
|
204
|
+
},
|
|
205
|
+
[FastLimit]: 0x100
|
|
206
|
+
} as Encoding<Uint8Array>
|
|
207
|
+
|
|
208
|
+
// -----------------------------------------------------------------------------
|
|
209
|
+
// Reading code points from a State
|
|
210
|
+
// -----------------------------------------------------------------------------
|
|
211
|
+
|
|
212
|
+
/** The width of the code point returned by the latest {@link next}. */
|
|
213
|
+
export let width = 0
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Decodes the code point at the cursor without consuming it. Returns the
|
|
217
|
+
* code point (setting {@link width}), {@link End}, {@link Invalid}, or
|
|
218
|
+
* {@link Truncated} (only for partial input; on complete input a truncated
|
|
219
|
+
* sequence is {@link Invalid}).
|
|
220
|
+
*/
|
|
221
|
+
export const next = (enc: Encoding<any>, state: State<any>, pos: number = state.pos): number => {
|
|
222
|
+
const end = state.end
|
|
223
|
+
if (pos >= end) return End
|
|
224
|
+
const cp = enc.decode(state.input, pos, end)
|
|
225
|
+
if (cp >= 0) {
|
|
226
|
+
width = enc.width(cp)
|
|
227
|
+
return cp
|
|
228
|
+
}
|
|
229
|
+
return cp === Truncated && !state.partial ? Invalid : cp
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Fails a parser that expected a code point but found `cp` (one of the
|
|
234
|
+
* negative results of {@link next}, or a code point that did not match).
|
|
235
|
+
*/
|
|
236
|
+
export const failAt = (
|
|
237
|
+
state: State<any>,
|
|
238
|
+
cp: number,
|
|
239
|
+
kind: Parameters<State["backtrack"]>[0],
|
|
240
|
+
expected: ReadonlyArray<string>,
|
|
241
|
+
offset: number = state.pos
|
|
242
|
+
): Fail => {
|
|
243
|
+
if (cp === End) return state.eoi(kind, expected, 1)
|
|
244
|
+
if (cp === Truncated) return state.incomplete(1, expected)
|
|
245
|
+
return state.backtrack(kind, expected, offset)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Results of `scan`, kept in module variables to avoid allocating
|
|
249
|
+
/** Offset reached by the latest {@link scan}. */
|
|
250
|
+
export let scanOffset = 0
|
|
251
|
+
/** Number of code points matched by the latest {@link scan}. */
|
|
252
|
+
export let scanCount = 0
|
|
253
|
+
/**
|
|
254
|
+
* Why the latest {@link scan} stopped: 0 = a non-matching code point or the
|
|
255
|
+
* maximum count, or one of {@link End}, {@link Invalid}, {@link Truncated}.
|
|
256
|
+
*/
|
|
257
|
+
export let scanStop = 0
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Scans code points from `pos` while `test(cp) !== negate`, up to `max`
|
|
261
|
+
* code points. `fast` is `fastLimit(enc)`, which callers can precompute.
|
|
262
|
+
*/
|
|
263
|
+
export const scan = (
|
|
264
|
+
enc: Encoding<any>,
|
|
265
|
+
state: State<any>,
|
|
266
|
+
test: (cp: number) => boolean,
|
|
267
|
+
negate: boolean,
|
|
268
|
+
max: number,
|
|
269
|
+
pos: number = state.pos,
|
|
270
|
+
fast: number = fastLimit(enc)
|
|
271
|
+
): void => {
|
|
272
|
+
const end = state.end
|
|
273
|
+
const decode = enc.decode
|
|
274
|
+
let i = pos
|
|
275
|
+
let count = 0
|
|
276
|
+
let stop = 0
|
|
277
|
+
if (state.inputKind === 0) {
|
|
278
|
+
const s = state.input as string
|
|
279
|
+
while (count < max) {
|
|
280
|
+
if (i >= end) {
|
|
281
|
+
stop = End
|
|
282
|
+
break
|
|
283
|
+
}
|
|
284
|
+
const c = s.charCodeAt(i)
|
|
285
|
+
if (c < fast) {
|
|
286
|
+
if (test(c) === negate) break
|
|
287
|
+
i++
|
|
288
|
+
} else {
|
|
289
|
+
const cp = decode(s, i, end)
|
|
290
|
+
if (cp < 0) {
|
|
291
|
+
stop = cp === Truncated && state.partial ? Truncated : Invalid
|
|
292
|
+
break
|
|
293
|
+
}
|
|
294
|
+
if (test(cp) === negate) break
|
|
295
|
+
i += enc.width(cp)
|
|
296
|
+
}
|
|
297
|
+
count++
|
|
298
|
+
}
|
|
299
|
+
} else {
|
|
300
|
+
const b = state.input as Uint8Array
|
|
301
|
+
while (count < max) {
|
|
302
|
+
if (i >= end) {
|
|
303
|
+
stop = End
|
|
304
|
+
break
|
|
305
|
+
}
|
|
306
|
+
const c = b[i]!
|
|
307
|
+
if (c < fast) {
|
|
308
|
+
if (test(c) === negate) break
|
|
309
|
+
i++
|
|
310
|
+
} else {
|
|
311
|
+
const cp = decode(b, i, end)
|
|
312
|
+
if (cp < 0) {
|
|
313
|
+
stop = cp === Truncated && state.partial ? Truncated : Invalid
|
|
314
|
+
break
|
|
315
|
+
}
|
|
316
|
+
if (test(cp) === negate) break
|
|
317
|
+
i += enc.width(cp)
|
|
318
|
+
}
|
|
319
|
+
count++
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
scanOffset = i
|
|
323
|
+
scanCount = count
|
|
324
|
+
scanStop = stop
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const always = () => true
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Checks that `[start, end)` is well formed, counting its code points.
|
|
331
|
+
* Returns the count, or `-1 - offset` of the first malformed sequence.
|
|
332
|
+
*/
|
|
333
|
+
export const validate = (enc: Encoding<any>, state: State<any>, start: number, end: number): number => {
|
|
334
|
+
const savedEnd = state.end
|
|
335
|
+
const savedPartial = state.partial
|
|
336
|
+
state.end = end
|
|
337
|
+
state.partial = false
|
|
338
|
+
scan(enc, state, always, false, Infinity, start)
|
|
339
|
+
state.end = savedEnd
|
|
340
|
+
state.partial = savedPartial
|
|
341
|
+
return scanStop === Invalid ? -1 - scanOffset : scanCount
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// -----------------------------------------------------------------------------
|
|
345
|
+
// Case folding
|
|
346
|
+
// -----------------------------------------------------------------------------
|
|
347
|
+
|
|
348
|
+
const foldCache = new Map<number, number>()
|
|
349
|
+
|
|
350
|
+
const single = (s: string): number => {
|
|
351
|
+
const cp = s.codePointAt(0)!
|
|
352
|
+
return s.length === (cp > 0xffff ? 2 : 1) ? cp : -1
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Simple case folding of a code point: the lowercase of its uppercase, when
|
|
357
|
+
* both map to a single code point (so `"K"` (Kelvin) and `"ſ"` fold to `"k"`
|
|
358
|
+
* and `"s"`), otherwise the code point itself.
|
|
359
|
+
*/
|
|
360
|
+
export const fold = (cp: number): number => {
|
|
361
|
+
if (cp < 128) return cp >= 65 && cp <= 90 ? cp + 32 : cp
|
|
362
|
+
let f = foldCache.get(cp)
|
|
363
|
+
if (f === undefined) {
|
|
364
|
+
const c = String.fromCodePoint(cp)
|
|
365
|
+
const upper = c.toUpperCase()
|
|
366
|
+
const viaUpper = single(upper) >= 0 ? single(upper.toLowerCase()) : -1
|
|
367
|
+
f = viaUpper >= 0 ? viaUpper : single(c.toLowerCase())
|
|
368
|
+
if (f < 0) f = cp
|
|
369
|
+
foldCache.set(cp, f)
|
|
370
|
+
}
|
|
371
|
+
return f
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// -----------------------------------------------------------------------------
|
|
375
|
+
// Error rendering
|
|
376
|
+
// -----------------------------------------------------------------------------
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Walks the code points of `[start, end)` leniently: malformed sequences
|
|
380
|
+
* are reported as `-1` and skipped one code unit at a time.
|
|
381
|
+
*/
|
|
382
|
+
export const walk = (
|
|
383
|
+
enc: Encoding<any>,
|
|
384
|
+
input: string | Uint8Array,
|
|
385
|
+
start: number,
|
|
386
|
+
end: number,
|
|
387
|
+
f: (cp: number, offset: number, width: number) => boolean | void
|
|
388
|
+
): void => {
|
|
389
|
+
let i = start
|
|
390
|
+
while (i < end) {
|
|
391
|
+
let cp = enc.decode(input, i, end)
|
|
392
|
+
let w: number
|
|
393
|
+
if (cp >= 0) {
|
|
394
|
+
w = enc.width(cp)
|
|
395
|
+
} else {
|
|
396
|
+
cp = -1
|
|
397
|
+
w = Math.min(enc.unit, end - i)
|
|
398
|
+
}
|
|
399
|
+
if (f(cp, i, w) === false) return
|
|
400
|
+
i += w
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/** Decodes `[start, end)`, replacing malformed sequences with U+FFFD. */
|
|
405
|
+
export const lenientText = (enc: Encoding<any>, input: string | Uint8Array, start: number, end: number): string => {
|
|
406
|
+
let out = ""
|
|
407
|
+
walk(enc, input, start, end, (cp) => {
|
|
408
|
+
out += cp < 0 ? "�" : charOf(cp)
|
|
409
|
+
})
|
|
410
|
+
return out
|
|
411
|
+
}
|