@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,409 @@
|
|
|
1
|
+
import type { Input, State } from "../Input.js"
|
|
2
|
+
|
|
3
|
+
/** A compiled token set. */
|
|
4
|
+
export interface Matcher {
|
|
5
|
+
/** Tests a code point (string input) or a byte (Uint8Array input). */
|
|
6
|
+
readonly code: (code: number) => boolean
|
|
7
|
+
/** Tests an element of an array input. */
|
|
8
|
+
readonly token: (token: unknown) => boolean
|
|
9
|
+
/** Human readable description, used in error messages. */
|
|
10
|
+
readonly description: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** @internal */
|
|
14
|
+
export interface TokenRangeLike {
|
|
15
|
+
readonly _tag: "TokenRange"
|
|
16
|
+
readonly from: string | number
|
|
17
|
+
readonly to: string | number
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const isRange = (u: unknown): u is TokenRangeLike =>
|
|
21
|
+
typeof u === "object" && u !== null && (u as any)._tag === "TokenRange"
|
|
22
|
+
|
|
23
|
+
const toCode = (u: string | number): number => typeof u === "number" ? u : u.codePointAt(0)!
|
|
24
|
+
|
|
25
|
+
/** @internal */
|
|
26
|
+
export const describeChar = (s: string): string => JSON.stringify(s)
|
|
27
|
+
|
|
28
|
+
const describeSet = (u: unknown): string => {
|
|
29
|
+
if (typeof u === "string") {
|
|
30
|
+
const chars = Array.from(u)
|
|
31
|
+
return chars.length === 1 ? describeChar(u) : `one of ${JSON.stringify(u)}`
|
|
32
|
+
}
|
|
33
|
+
if (typeof u === "number") return `0x${u.toString(16).padStart(2, "0")}`
|
|
34
|
+
if (typeof u === "function") return u.name ? u.name : "a matching token"
|
|
35
|
+
if (u instanceof Uint8Array) return `one of ${describeBytes(u)}`
|
|
36
|
+
if (isRange(u)) return `${describeSet(u.from)}..=${describeSet(u.to)}`
|
|
37
|
+
if (Array.isArray(u)) return u.map(describeSet).join(" or ")
|
|
38
|
+
return JSON.stringify(u)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Renders bytes as a Rust-style byte string literal: b"GET\r\n". */
|
|
42
|
+
export const describeBytes = (bytes: Uint8Array): string => {
|
|
43
|
+
let out = "b\""
|
|
44
|
+
for (const b of bytes) {
|
|
45
|
+
if (b === 0x22) out += "\\\""
|
|
46
|
+
else if (b === 0x5c) out += "\\\\"
|
|
47
|
+
else if (b === 0x0a) out += "\\n"
|
|
48
|
+
else if (b === 0x0d) out += "\\r"
|
|
49
|
+
else if (b === 0x09) out += "\\t"
|
|
50
|
+
else if (b >= 0x20 && b < 0x7f) out += String.fromCharCode(b)
|
|
51
|
+
else out += `\\x${b.toString(16).padStart(2, "0")}`
|
|
52
|
+
}
|
|
53
|
+
return out + "\""
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const compileCode = (u: unknown): (code: number) => boolean => {
|
|
57
|
+
if (typeof u === "string") {
|
|
58
|
+
const codes = Array.from(u, (c) => c.codePointAt(0)!)
|
|
59
|
+
if (codes.length === 1) {
|
|
60
|
+
const c = codes[0]!
|
|
61
|
+
return (x) => x === c
|
|
62
|
+
}
|
|
63
|
+
return bitmap(codes)
|
|
64
|
+
}
|
|
65
|
+
if (typeof u === "number") return (x) => x === u
|
|
66
|
+
if (typeof u === "function") return u as (code: number) => boolean
|
|
67
|
+
if (u instanceof Uint8Array) return bitmap(Array.from(u))
|
|
68
|
+
if (isRange(u)) {
|
|
69
|
+
const from = toCode(u.from)
|
|
70
|
+
const to = toCode(u.to)
|
|
71
|
+
return (x) => x >= from && x <= to
|
|
72
|
+
}
|
|
73
|
+
if (Array.isArray(u)) {
|
|
74
|
+
const tests = u.map(compileCode)
|
|
75
|
+
if (tests.length === 1) return tests[0]!
|
|
76
|
+
return (x) => {
|
|
77
|
+
for (let i = 0; i < tests.length; i++) if (tests[i]!(x)) return true
|
|
78
|
+
return false
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
throw new TypeError(`invalid token set: ${String(u)}`)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const bitmap = (codes: ReadonlyArray<number>): (code: number) => boolean => {
|
|
85
|
+
const ascii = new Uint8Array(256)
|
|
86
|
+
let other: Set<number> | undefined
|
|
87
|
+
for (const c of codes) {
|
|
88
|
+
if (c < 256) ascii[c] = 1
|
|
89
|
+
else (other ??= new Set()).add(c)
|
|
90
|
+
}
|
|
91
|
+
if (other === undefined) return (x) => x < 256 && ascii[x] === 1
|
|
92
|
+
const set = other
|
|
93
|
+
return (x) => x < 256 ? ascii[x] === 1 : set.has(x)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const compileToken = (u: unknown): (token: unknown) => boolean => {
|
|
97
|
+
if (typeof u === "function") return u as (token: unknown) => boolean
|
|
98
|
+
if (Array.isArray(u)) {
|
|
99
|
+
const set = new Set(u)
|
|
100
|
+
return (t) => set.has(t)
|
|
101
|
+
}
|
|
102
|
+
return (t) => t === u
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Marks a predicate over characters that also accepts a code point (a
|
|
107
|
+
* number), so string inputs can call it without allocating a string per
|
|
108
|
+
* character.
|
|
109
|
+
*/
|
|
110
|
+
export const CodePointPredicate: unique symbol = Symbol.for("effect-parse/CodePointPredicate")
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* The code points accepted by a predicate, as sorted, disjoint, inclusive
|
|
114
|
+
* ranges, when they are known. The compiler turns such predicates into
|
|
115
|
+
* comparisons or tables.
|
|
116
|
+
*/
|
|
117
|
+
export const PredicateRanges: unique symbol = Symbol.for("effect-parse/PredicateRanges")
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Marks `f` as a {@link CodePointPredicate} named `name` (used in error
|
|
121
|
+
* messages), optionally recording the code point ranges it accepts.
|
|
122
|
+
*/
|
|
123
|
+
export const codePointPredicate = <F extends (c: never) => boolean>(
|
|
124
|
+
name: string,
|
|
125
|
+
f: F,
|
|
126
|
+
ranges?: ReadonlyArray<readonly [number, number]>
|
|
127
|
+
): F => {
|
|
128
|
+
Object.defineProperty(f, "name", { value: name })
|
|
129
|
+
;(f as any)[CodePointPredicate] = true
|
|
130
|
+
if (ranges !== undefined) (f as any)[PredicateRanges] = ranges
|
|
131
|
+
return f
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Compiles a token set. Predicates for string inputs receive the token as a
|
|
136
|
+
* string, so they are wrapped here.
|
|
137
|
+
*/
|
|
138
|
+
export const compile = (set: unknown, kind: "string" | "code" = "code"): Matcher => {
|
|
139
|
+
const description = describeSet(set)
|
|
140
|
+
let code: (code: number) => boolean
|
|
141
|
+
if (typeof set === "function" && kind === "string" && !(CodePointPredicate in set)) {
|
|
142
|
+
const pred = set as (c: string) => boolean
|
|
143
|
+
code = (x) => pred(String.fromCodePoint(x))
|
|
144
|
+
} else {
|
|
145
|
+
code = compileCodeSafe(set, kind)
|
|
146
|
+
}
|
|
147
|
+
return { code, token: compileToken(set), description }
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const compileCodeSafe = (set: unknown, kind: "string" | "code"): (code: number) => boolean => {
|
|
151
|
+
if (Array.isArray(set) && kind === "string") {
|
|
152
|
+
// Predicates nested in arrays also take strings
|
|
153
|
+
const tests = set.map((s) => compile(s, kind).code)
|
|
154
|
+
return (x) => {
|
|
155
|
+
for (let i = 0; i < tests.length; i++) if (tests[i]!(x)) return true
|
|
156
|
+
return false
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
try {
|
|
160
|
+
return compileCode(set)
|
|
161
|
+
} catch {
|
|
162
|
+
return () => false
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* A matcher that works for every input kind. String predicates receive the
|
|
168
|
+
* token as a string, byte predicates receive numbers.
|
|
169
|
+
*/
|
|
170
|
+
export interface AnyMatcher {
|
|
171
|
+
readonly forString: Matcher
|
|
172
|
+
readonly forCode: Matcher
|
|
173
|
+
readonly description: string
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export const compileAny = (set: unknown): AnyMatcher => {
|
|
177
|
+
const forCode = compile(set, "code")
|
|
178
|
+
const needsString = typeof set === "function" || Array.isArray(set)
|
|
179
|
+
return {
|
|
180
|
+
forString: needsString ? compile(set, "string") : forCode,
|
|
181
|
+
forCode,
|
|
182
|
+
description: forCode.description
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Whether the string token at `i` is a high surrogate at the very end of a
|
|
188
|
+
* partial input, i.e. the first half of a code point whose second half has
|
|
189
|
+
* not arrived yet.
|
|
190
|
+
*/
|
|
191
|
+
export const splitSurrogate = (state: State<Input>, i: number): boolean => {
|
|
192
|
+
if (!state.partial || i !== state.end - 1) return false
|
|
193
|
+
const c = (state.input as string).charCodeAt(i)
|
|
194
|
+
return c >= 0xd800 && c <= 0xdbff
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** Reads the code point at `i` of a string. */
|
|
198
|
+
export const codePointAt = (s: string, i: number): number => {
|
|
199
|
+
const c = s.charCodeAt(i)
|
|
200
|
+
if (c >= 0xd800 && c <= 0xdbff && i + 1 < s.length) {
|
|
201
|
+
const d = s.charCodeAt(i + 1)
|
|
202
|
+
if (d >= 0xdc00 && d <= 0xdfff) return ((c - 0xd800) << 10) + (d - 0xdc00) + 0x10000
|
|
203
|
+
}
|
|
204
|
+
return c
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Tests the token at the current position. Returns the token (as the input's
|
|
209
|
+
* token type) and advances, or returns `NoMatch`/`End`.
|
|
210
|
+
*/
|
|
211
|
+
export const NoMatch: unique symbol = Symbol("NoMatch")
|
|
212
|
+
export const End: unique symbol = Symbol("End")
|
|
213
|
+
|
|
214
|
+
export const nextIf = (
|
|
215
|
+
state: State<Input>,
|
|
216
|
+
m: AnyMatcher,
|
|
217
|
+
negate: boolean
|
|
218
|
+
): unknown => {
|
|
219
|
+
const pos = state.pos
|
|
220
|
+
if (pos >= state.end) return End
|
|
221
|
+
switch (state.inputKind) {
|
|
222
|
+
case 0: {
|
|
223
|
+
const s = state.input as string
|
|
224
|
+
if (splitSurrogate(state, pos)) return End
|
|
225
|
+
const cp = codePointAt(s, pos)
|
|
226
|
+
if (m.forString.code(cp) === negate) return NoMatch
|
|
227
|
+
const len = cp > 0xffff ? 2 : 1
|
|
228
|
+
state.pos = pos + len
|
|
229
|
+
return len === 1 ? s[pos] : s.slice(pos, pos + 2)
|
|
230
|
+
}
|
|
231
|
+
case 1: {
|
|
232
|
+
const b = (state.input as Uint8Array)[pos]!
|
|
233
|
+
if (m.forCode.code(b) === negate) return NoMatch
|
|
234
|
+
state.pos = pos + 1
|
|
235
|
+
return b
|
|
236
|
+
}
|
|
237
|
+
default: {
|
|
238
|
+
const t = (state.input as ReadonlyArray<unknown>)[pos]
|
|
239
|
+
if (m.forCode.token(t) === negate) return NoMatch
|
|
240
|
+
state.pos = pos + 1
|
|
241
|
+
return t
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Scans forward while tokens satisfy the matcher (xor `negate`), up to `max`
|
|
248
|
+
* tokens. Returns the offset reached, the number of tokens matched, and
|
|
249
|
+
* whether the end of input was hit before `max` tokens.
|
|
250
|
+
*/
|
|
251
|
+
export const scan = (
|
|
252
|
+
state: State<Input>,
|
|
253
|
+
m: AnyMatcher,
|
|
254
|
+
negate: boolean,
|
|
255
|
+
max: number
|
|
256
|
+
): { readonly offset: number; readonly count: number; readonly hitEnd: boolean } => {
|
|
257
|
+
let i = state.pos
|
|
258
|
+
const end = state.end
|
|
259
|
+
let count = 0
|
|
260
|
+
switch (state.inputKind) {
|
|
261
|
+
case 0: {
|
|
262
|
+
const s = state.input as string
|
|
263
|
+
const test = m.forString.code
|
|
264
|
+
while (count < max) {
|
|
265
|
+
if (i >= end) return { offset: i, count, hitEnd: true }
|
|
266
|
+
const c = s.charCodeAt(i)
|
|
267
|
+
if (c < 0xd800 || c > 0xdbff) {
|
|
268
|
+
if (test(c) === negate) break
|
|
269
|
+
i++
|
|
270
|
+
} else {
|
|
271
|
+
if (splitSurrogate(state, i)) return { offset: i, count, hitEnd: true }
|
|
272
|
+
const cp = codePointAt(s, i)
|
|
273
|
+
if (test(cp) === negate) break
|
|
274
|
+
i += cp > 0xffff ? 2 : 1
|
|
275
|
+
}
|
|
276
|
+
count++
|
|
277
|
+
}
|
|
278
|
+
return { offset: i, count, hitEnd: false }
|
|
279
|
+
}
|
|
280
|
+
case 1: {
|
|
281
|
+
const bytes = state.input as Uint8Array
|
|
282
|
+
const test = m.forCode.code
|
|
283
|
+
while (count < max) {
|
|
284
|
+
if (i >= end) return { offset: i, count, hitEnd: true }
|
|
285
|
+
if (test(bytes[i]!) === negate) break
|
|
286
|
+
i++
|
|
287
|
+
count++
|
|
288
|
+
}
|
|
289
|
+
return { offset: i, count, hitEnd: false }
|
|
290
|
+
}
|
|
291
|
+
default: {
|
|
292
|
+
const arr = state.input as ReadonlyArray<unknown>
|
|
293
|
+
const test = m.forCode.token
|
|
294
|
+
while (count < max) {
|
|
295
|
+
if (i >= end) return { offset: i, count, hitEnd: true }
|
|
296
|
+
if (test(arr[i]) === negate) break
|
|
297
|
+
i++
|
|
298
|
+
count++
|
|
299
|
+
}
|
|
300
|
+
return { offset: i, count, hitEnd: false }
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/** Advances `n` tokens from `start`, returning the offset reached and the count. */
|
|
306
|
+
export const advance = (state: State<Input>, start: number, n: number): { offset: number; count: number } => {
|
|
307
|
+
if (state.inputKind !== 0) {
|
|
308
|
+
const offset = Math.min(state.end, start + n)
|
|
309
|
+
return { offset, count: offset - start }
|
|
310
|
+
}
|
|
311
|
+
const s = state.input as string
|
|
312
|
+
let i = start
|
|
313
|
+
let count = 0
|
|
314
|
+
while (count < n && i < state.end) {
|
|
315
|
+
if (splitSurrogate(state, i)) break
|
|
316
|
+
const c = s.charCodeAt(i)
|
|
317
|
+
i += c >= 0xd800 && c <= 0xdbff && i + 1 < s.length && (s.charCodeAt(i + 1) & 0xfc00) === 0xdc00 ? 2 : 1
|
|
318
|
+
count++
|
|
319
|
+
}
|
|
320
|
+
return { offset: i, count }
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/** Counts tokens between two offsets. */
|
|
324
|
+
export const countTokens = (state: State<Input>, start: number, end: number): number => {
|
|
325
|
+
if (state.inputKind !== 0) return end - start
|
|
326
|
+
const s = state.input as string
|
|
327
|
+
let count = 0
|
|
328
|
+
for (let i = start; i < end; i++) {
|
|
329
|
+
const c = s.charCodeAt(i)
|
|
330
|
+
if (c >= 0xd800 && c <= 0xdbff && i + 1 < end && (s.charCodeAt(i + 1) & 0xfc00) === 0xdc00) i++
|
|
331
|
+
count++
|
|
332
|
+
}
|
|
333
|
+
return count
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/** Finds `needle` in the input at or after `from`; returns -1 if absent. */
|
|
337
|
+
export const indexOf = (state: State<Input>, needle: Input, from: number): number => {
|
|
338
|
+
switch (state.inputKind) {
|
|
339
|
+
case 0:
|
|
340
|
+
return (state.input as string).indexOf(needle as string, from)
|
|
341
|
+
case 1: {
|
|
342
|
+
const hay = state.input as Uint8Array
|
|
343
|
+
const n = needle as Uint8Array
|
|
344
|
+
if (n.length === 0) return from <= hay.length ? from : -1
|
|
345
|
+
const first = n[0]!
|
|
346
|
+
let i = hay.indexOf(first, from)
|
|
347
|
+
while (i !== -1 && i + n.length <= hay.length) {
|
|
348
|
+
let j = 1
|
|
349
|
+
while (j < n.length && hay[i + j] === n[j]) j++
|
|
350
|
+
if (j === n.length) return i
|
|
351
|
+
i = hay.indexOf(first, i + 1)
|
|
352
|
+
}
|
|
353
|
+
return -1
|
|
354
|
+
}
|
|
355
|
+
default: {
|
|
356
|
+
const hay = state.input as ReadonlyArray<unknown>
|
|
357
|
+
const n = needle as ReadonlyArray<unknown>
|
|
358
|
+
outer: for (let i = from; i + n.length <= hay.length; i++) {
|
|
359
|
+
for (let j = 0; j < n.length; j++) if (hay[i + j] !== n[j]) continue outer
|
|
360
|
+
return i
|
|
361
|
+
}
|
|
362
|
+
return -1
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Compares the input at the current position with a literal. Returns the
|
|
369
|
+
* number of matching units if the literal matches completely (its length),
|
|
370
|
+
* -1 on mismatch, or -2 when the input ends while matching a prefix.
|
|
371
|
+
*/
|
|
372
|
+
export const matchLiteral = (state: State<Input>, lit: Input, caseless: boolean): number => {
|
|
373
|
+
const pos = state.pos
|
|
374
|
+
const len = lit.length
|
|
375
|
+
const avail = state.end - pos
|
|
376
|
+
const n = Math.min(len, avail)
|
|
377
|
+
switch (state.inputKind) {
|
|
378
|
+
case 0: {
|
|
379
|
+
const s = state.input as string
|
|
380
|
+
const l = lit as string
|
|
381
|
+
if (!caseless) {
|
|
382
|
+
if (avail >= len) return s.startsWith(l, pos) ? len : -1
|
|
383
|
+
return l.startsWith(s.slice(pos)) ? -2 : -1
|
|
384
|
+
}
|
|
385
|
+
for (let i = 0; i < n; i++) {
|
|
386
|
+
if (lowerAscii(s.charCodeAt(pos + i)) !== lowerAscii(l.charCodeAt(i))) return -1
|
|
387
|
+
}
|
|
388
|
+
return n === len ? len : -2
|
|
389
|
+
}
|
|
390
|
+
case 1: {
|
|
391
|
+
const b = state.input as Uint8Array
|
|
392
|
+
const l = lit as Uint8Array
|
|
393
|
+
for (let i = 0; i < n; i++) {
|
|
394
|
+
const x = b[pos + i]!
|
|
395
|
+
const y = l[i]!
|
|
396
|
+
if (caseless ? lowerAscii(x) !== lowerAscii(y) : x !== y) return -1
|
|
397
|
+
}
|
|
398
|
+
return n === len ? len : -2
|
|
399
|
+
}
|
|
400
|
+
default: {
|
|
401
|
+
const a = state.input as ReadonlyArray<unknown>
|
|
402
|
+
const l = lit as ReadonlyArray<unknown>
|
|
403
|
+
for (let i = 0; i < n; i++) if (a[pos + i] !== l[i]) return -1
|
|
404
|
+
return n === len ? len : -2
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const lowerAscii = (c: number): number => c >= 65 && c <= 90 ? c + 32 : c
|