@thomasfosterau/effect-parse 0.4.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -13
- package/dist/Ascii.d.ts +11 -4
- package/dist/Ascii.d.ts.map +1 -1
- package/dist/Ascii.js +7 -1
- package/dist/Ascii.js.map +1 -1
- package/dist/Binary.d.ts.map +1 -1
- package/dist/Binary.js +64 -35
- package/dist/Binary.js.map +1 -1
- package/dist/Bits.d.ts.map +1 -1
- package/dist/Bits.js +7 -4
- package/dist/Bits.js.map +1 -1
- package/dist/Compiler.d.ts +74 -2
- package/dist/Compiler.d.ts.map +1 -1
- package/dist/Compiler.js +78 -1
- package/dist/Compiler.js.map +1 -1
- package/dist/Expression.d.ts.map +1 -1
- package/dist/Expression.js +86 -74
- package/dist/Expression.js.map +1 -1
- package/dist/Input.d.ts +37 -4
- package/dist/Input.d.ts.map +1 -1
- package/dist/Input.js +42 -0
- package/dist/Input.js.map +1 -1
- package/dist/Nom.d.ts +5 -15
- package/dist/Nom.d.ts.map +1 -1
- package/dist/Nom.js.map +1 -1
- package/dist/ParseError.d.ts.map +1 -1
- package/dist/ParseError.js.map +1 -1
- package/dist/Parser.d.ts +366 -26
- package/dist/Parser.d.ts.map +1 -1
- package/dist/Parser.js +390 -19
- package/dist/Parser.js.map +1 -1
- package/dist/ParserSchema.js +1 -1
- package/dist/ParserStream.d.ts +3 -2
- package/dist/ParserStream.d.ts.map +1 -1
- package/dist/ParserStream.js +13 -9
- package/dist/ParserStream.js.map +1 -1
- package/dist/Syntax.d.ts +23 -5
- package/dist/Syntax.d.ts.map +1 -1
- package/dist/Syntax.js.map +1 -1
- package/dist/Text.d.ts.map +1 -1
- package/dist/Text.js +9 -9
- package/dist/Text.js.map +1 -1
- package/dist/Token.d.ts +55 -10
- package/dist/Token.d.ts.map +1 -1
- package/dist/Token.js +59 -21
- package/dist/Token.js.map +1 -1
- package/dist/internal/compiler/closure.d.ts.map +1 -1
- package/dist/internal/compiler/closure.js +257 -97
- package/dist/internal/compiler/closure.js.map +1 -1
- package/dist/internal/compiler/entries.d.ts +3 -1
- package/dist/internal/compiler/entries.d.ts.map +1 -1
- package/dist/internal/compiler/entries.js +3 -2
- package/dist/internal/compiler/entries.js.map +1 -1
- package/dist/internal/compiler/first.d.ts.map +1 -1
- package/dist/internal/compiler/first.js +7 -1
- package/dist/internal/compiler/first.js.map +1 -1
- package/dist/internal/compiler/graph.d.ts +17 -0
- package/dist/internal/compiler/graph.d.ts.map +1 -0
- package/dist/internal/compiler/graph.js +56 -0
- package/dist/internal/compiler/graph.js.map +1 -0
- package/dist/internal/compiler/jit.d.ts +5 -0
- package/dist/internal/compiler/jit.d.ts.map +1 -1
- package/dist/internal/compiler/jit.js +185 -96
- package/dist/internal/compiler/jit.js.map +1 -1
- package/dist/internal/compiler/registry.d.ts +10 -2
- package/dist/internal/compiler/registry.d.ts.map +1 -1
- package/dist/internal/compiler/registry.js.map +1 -1
- package/dist/internal/compiler/search.d.ts +32 -0
- package/dist/internal/compiler/search.d.ts.map +1 -0
- package/dist/internal/compiler/search.js +68 -0
- package/dist/internal/compiler/search.js.map +1 -0
- package/dist/internal/compiler/sets.d.ts.map +1 -1
- package/dist/internal/compiler/sets.js +1 -1
- package/dist/internal/compiler/sets.js.map +1 -1
- package/dist/internal/compiler/wasm.d.ts +8 -0
- package/dist/internal/compiler/wasm.d.ts.map +1 -0
- package/dist/internal/compiler/wasm.js +2769 -0
- package/dist/internal/compiler/wasm.js.map +1 -0
- package/dist/internal/compiler/wasmAsm.d.ts +199 -0
- package/dist/internal/compiler/wasmAsm.d.ts.map +1 -0
- package/dist/internal/compiler/wasmAsm.js +427 -0
- package/dist/internal/compiler/wasmAsm.js.map +1 -0
- package/dist/internal/entries.d.ts +3 -1
- package/dist/internal/entries.d.ts.map +1 -1
- package/dist/internal/entries.js.map +1 -1
- package/dist/internal/node.d.ts +58 -2
- package/dist/internal/node.d.ts.map +1 -1
- package/dist/internal/node.js +13 -3
- package/dist/internal/node.js.map +1 -1
- package/dist/internal/numbers.d.ts.map +1 -1
- package/dist/internal/numbers.js.map +1 -1
- package/dist/internal/run.d.ts +12 -0
- package/dist/internal/run.d.ts.map +1 -1
- package/dist/internal/run.js +51 -11
- package/dist/internal/run.js.map +1 -1
- package/dist/internal/stack.d.ts +6 -0
- package/dist/internal/stack.d.ts.map +1 -1
- package/dist/internal/stack.js +8 -2
- package/dist/internal/stack.js.map +1 -1
- package/dist/internal/syntax.d.ts +4 -4
- package/dist/internal/syntax.d.ts.map +1 -1
- package/dist/internal/syntax.js +18 -10
- package/dist/internal/syntax.js.map +1 -1
- package/dist/internal/text.d.ts +32 -0
- package/dist/internal/text.d.ts.map +1 -1
- package/dist/internal/text.js +68 -14
- package/dist/internal/text.js.map +1 -1
- package/dist/internal/tokens.d.ts +13 -0
- package/dist/internal/tokens.d.ts.map +1 -1
- package/dist/internal/tokens.js +59 -39
- package/dist/internal/tokens.js.map +1 -1
- package/package.json +5 -4
- package/src/Ascii.ts +42 -9
- package/src/Binary.ts +151 -71
- package/src/Bits.ts +12 -6
- package/src/Compiler.ts +101 -10
- package/src/Expression.ts +94 -81
- package/src/Input.ts +65 -6
- package/src/Nom.ts +15 -5
- package/src/ParseError.ts +3 -0
- package/src/Parser.ts +678 -127
- package/src/ParserStream.ts +37 -18
- package/src/Syntax.ts +60 -36
- package/src/Text.ts +16 -15
- package/src/Token.ts +138 -29
- package/src/internal/compiler/closure.ts +362 -135
- package/src/internal/compiler/entries.ts +4 -2
- package/src/internal/compiler/first.ts +10 -0
- package/src/internal/compiler/graph.ts +68 -0
- package/src/internal/compiler/jit.ts +284 -154
- package/src/internal/compiler/registry.ts +10 -2
- package/src/internal/compiler/search.ts +68 -0
- package/src/internal/compiler/sets.ts +7 -3
- package/src/internal/compiler/wasm.ts +3203 -0
- package/src/internal/compiler/wasmAsm.ts +484 -0
- package/src/internal/entries.ts +8 -2
- package/src/internal/node.ts +54 -4
- package/src/internal/numbers.ts +3 -1
- package/src/internal/run.ts +71 -12
- package/src/internal/stack.ts +8 -1
- package/src/internal/syntax.ts +39 -18
- package/src/internal/text.ts +97 -9
- package/src/internal/tokens.ts +96 -14
package/src/internal/run.ts
CHANGED
|
@@ -24,18 +24,77 @@ export const runOn = <A>(
|
|
|
24
24
|
state: State<any>,
|
|
25
25
|
diagnostics: boolean = true
|
|
26
26
|
): A | Fail => {
|
|
27
|
-
if (state.tracer
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
27
|
+
if (state.tracer !== undefined || state.recovered !== undefined || state.syntax !== undefined) {
|
|
28
|
+
return Stack.run(step, state)
|
|
29
|
+
}
|
|
30
|
+
const compiled = Registry.compiled(p, state.inputKind, state.partial || state.resume !== undefined)
|
|
31
|
+
return compiled === undefined ? Stack.run(step, state) : attempt(compiled, step, state, diagnostics)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Runs a compiled step on `state`: directly when it guards against
|
|
36
|
+
* call-stack overflows itself (the JIT's), else under `Stack.run`.
|
|
37
|
+
*/
|
|
38
|
+
export const runCompiled = <A>(step: (state: State<any>) => A, guarded: boolean, state: State<any>): A | Fail =>
|
|
39
|
+
guarded ? step(state) : Stack.run(step, state)
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Runs the compiled step of `compiled` (or, if compiling failed, the
|
|
43
|
+
* parser's); if it fails, rewinds and, with `diagnostics`, runs the
|
|
44
|
+
* parser's step to record the failure.
|
|
45
|
+
*/
|
|
46
|
+
const attempt = <A>(
|
|
47
|
+
compiled: Registry.Compiled,
|
|
48
|
+
step: (state: State<any>) => A | Fail,
|
|
49
|
+
state: State<any>,
|
|
50
|
+
diagnostics: boolean
|
|
51
|
+
): A | Fail => {
|
|
52
|
+
const c = compiled.step
|
|
53
|
+
if (c === undefined) return Stack.run(step, state)
|
|
54
|
+
const start = state.pos
|
|
55
|
+
const a = runCompiled(c, compiled.guarded, state)
|
|
56
|
+
if (a !== Fail) {
|
|
57
|
+
// SAFETY: the compiled step returns what the parser's step returns
|
|
58
|
+
return a as A
|
|
59
|
+
}
|
|
60
|
+
state.rewind(start)
|
|
61
|
+
return diagnostics ? Stack.run(step, state) : Fail
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** What `prepare` holds before its first run: nothing compiled. */
|
|
65
|
+
const uncompiled: Registry.Compiled = {
|
|
66
|
+
backend: "interpreted",
|
|
67
|
+
step: undefined,
|
|
68
|
+
guarded: false,
|
|
69
|
+
fallbacks: [],
|
|
70
|
+
error: undefined
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* `runOn(p, p.step, state, diagnostics)` as a function of the state, with
|
|
75
|
+
* the per-run lookup done once: the compiled step is looked up again only
|
|
76
|
+
* when the input kind or the build (plain or resumable) changes from the
|
|
77
|
+
* previous run.
|
|
78
|
+
*/
|
|
79
|
+
export const prepare = <A>(
|
|
80
|
+
p: ParserLike,
|
|
81
|
+
step: (state: State<any>) => A | Fail,
|
|
82
|
+
diagnostics: boolean
|
|
83
|
+
): ((state: State<any>) => A | Fail) => {
|
|
84
|
+
// The build of the previous run: its input kind (-1 before the first run) and whether it is resumable
|
|
85
|
+
let kind = -1
|
|
86
|
+
let resumable = false
|
|
87
|
+
let compiled = uncompiled
|
|
88
|
+
return (state) => {
|
|
89
|
+
if (state.tracer !== undefined || state.recovered !== undefined || state.syntax !== undefined) {
|
|
90
|
+
return Stack.run(step, state)
|
|
91
|
+
}
|
|
92
|
+
const r = state.partial || state.resume !== undefined
|
|
93
|
+
if (state.inputKind !== kind || r !== resumable) {
|
|
94
|
+
compiled = Registry.compiled(p, state.inputKind, r) ?? uncompiled
|
|
95
|
+
kind = state.inputKind
|
|
96
|
+
resumable = r
|
|
38
97
|
}
|
|
98
|
+
return attempt(compiled, step, state, diagnostics)
|
|
39
99
|
}
|
|
40
|
-
return Stack.run(step, state)
|
|
41
100
|
}
|
package/src/internal/stack.ts
CHANGED
|
@@ -36,6 +36,13 @@ export const run = <S extends State<any>, A>(step: (state: S) => A, state: S): A
|
|
|
36
36
|
return step(state)
|
|
37
37
|
} catch (e) {
|
|
38
38
|
if (!isStackOverflow(e)) throw e
|
|
39
|
-
return state
|
|
39
|
+
return tooDeep(state)
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Records the `TooDeep` failure of a call-stack overflow. The JIT's
|
|
45
|
+
* generated entry catches overflows itself, with this, so that the runners
|
|
46
|
+
* can call it without {@link run}.
|
|
47
|
+
*/
|
|
48
|
+
export const tooDeep = (state: State<any>): Fail => state.failMessage("Cut", "TooDeep", tooDeepMessage)
|
package/src/internal/syntax.ts
CHANGED
|
@@ -105,9 +105,13 @@ const rebase = (f: Failure, start: number): Failure =>
|
|
|
105
105
|
/** Moves a failure stored relative to a node back to absolute offsets. */
|
|
106
106
|
export const absolute = (f: Failure, start: number): Failure => rebase(f, -start)
|
|
107
107
|
|
|
108
|
+
/** The children, and their offsets, of every leaf: one shared empty array. */
|
|
109
|
+
const none: ReadonlyArray<never> = []
|
|
110
|
+
|
|
108
111
|
/**
|
|
109
112
|
* Builds a green node over `[start, end)` from the children waiting on the
|
|
110
|
-
* stack
|
|
113
|
+
* stack, `entries` from index `from` on (which the caller then removes):
|
|
114
|
+
* slicing them off first would allocate an array per node.
|
|
111
115
|
*/
|
|
112
116
|
const build = (
|
|
113
117
|
kind: string,
|
|
@@ -117,24 +121,40 @@ const build = (
|
|
|
117
121
|
end: number,
|
|
118
122
|
lookahead: number,
|
|
119
123
|
entries: ReadonlyArray<Child>,
|
|
124
|
+
from: number,
|
|
120
125
|
value: unknown,
|
|
121
126
|
error: ErrorInfo | undefined,
|
|
122
127
|
diagnostics?: Diagnostics
|
|
123
128
|
): Green => {
|
|
124
|
-
const n = entries.length
|
|
129
|
+
const n = entries.length - from
|
|
130
|
+
const width = end - start
|
|
131
|
+
const looked = Math.max(lookahead, end) - start
|
|
132
|
+
if (n === 0)
|
|
133
|
+
return new Green(
|
|
134
|
+
kind,
|
|
135
|
+
named,
|
|
136
|
+
key,
|
|
137
|
+
width,
|
|
138
|
+
looked,
|
|
139
|
+
none,
|
|
140
|
+
none,
|
|
141
|
+
undefined,
|
|
142
|
+
value,
|
|
143
|
+
error,
|
|
144
|
+
error !== undefined,
|
|
145
|
+
diagnostics
|
|
146
|
+
)
|
|
125
147
|
const children = new Array<Green>(n)
|
|
126
148
|
const offsets = new Array<number>(n)
|
|
127
149
|
let fields: Array<string | undefined> | undefined
|
|
128
150
|
let hasError = error !== undefined
|
|
129
151
|
for (let i = 0; i < n; i++) {
|
|
130
|
-
const e = entries[i]
|
|
152
|
+
const e = entries[from + i]
|
|
131
153
|
children[i] = e.green
|
|
132
154
|
offsets[i] = e.start - start
|
|
133
155
|
if (e.green.hasError) hasError = true
|
|
134
156
|
if (e.field !== undefined) (fields ??= new Array(n).fill(undefined))[i] = e.field
|
|
135
157
|
}
|
|
136
|
-
const width = end - start
|
|
137
|
-
const looked = Math.max(lookahead, end) - start
|
|
138
158
|
return new Green(kind, named, key, width, looked, children, offsets, fields, value, error, hasError, diagnostics)
|
|
139
159
|
}
|
|
140
160
|
|
|
@@ -162,8 +182,8 @@ export interface Previous {
|
|
|
162
182
|
|
|
163
183
|
/** The hooks that `Syntax` combinators and `Parser.resumeAfter`/`retryAfter` call. */
|
|
164
184
|
export interface Builder {
|
|
165
|
-
node(state: State<any>, step: (state: State<any>) =>
|
|
166
|
-
field(state: State<any>, step: (state: State<any>) =>
|
|
185
|
+
node<A>(state: State<any>, step: (state: State<any>) => A, key: Key, kind: string, named: boolean): A
|
|
186
|
+
field<A>(state: State<any>, step: (state: State<any>) => A, name: string): A
|
|
167
187
|
/** After error recovery: `[start, pos)` was skipped after `failure`. */
|
|
168
188
|
skipped(start: number, failure: Failure, mode: ErrMode): void
|
|
169
189
|
/**
|
|
@@ -224,7 +244,7 @@ export class SyntaxBuilder implements Builder {
|
|
|
224
244
|
return this.state.pos > this.highWater ? this.state.pos : this.highWater
|
|
225
245
|
}
|
|
226
246
|
|
|
227
|
-
node(state: State<any>, step: (state: State<any>) =>
|
|
247
|
+
node<A>(state: State<any>, step: (state: State<any>) => A, key: Key, kind: string, named: boolean): A {
|
|
228
248
|
const start = this.state.pos
|
|
229
249
|
const stack = this.stack
|
|
230
250
|
if (this.previous !== undefined) {
|
|
@@ -241,7 +261,8 @@ export class SyntaxBuilder implements Builder {
|
|
|
241
261
|
if (d.hintExpected !== undefined) this.state.setHint(start + d.hintOffset, d.hintExpected)
|
|
242
262
|
}
|
|
243
263
|
stack.push(new Child(green, start, end, undefined))
|
|
244
|
-
|
|
264
|
+
// SAFETY: `reusable` returns only a node built under this `key`, by this parser's `step`, whose value it holds
|
|
265
|
+
return green.value as A
|
|
245
266
|
}
|
|
246
267
|
}
|
|
247
268
|
const mark = stack.length
|
|
@@ -255,7 +276,7 @@ export class SyntaxBuilder implements Builder {
|
|
|
255
276
|
const a = step(state)
|
|
256
277
|
const looked = this.looked()
|
|
257
278
|
this.highWater = outer > looked ? outer : looked
|
|
258
|
-
const deepest = this.state.deepest
|
|
279
|
+
const deepest = this.state.deepest
|
|
259
280
|
this.state.deepest = outerDeepest
|
|
260
281
|
if (deepest !== undefined) this.deepen(deepest)
|
|
261
282
|
if (a === Fail) {
|
|
@@ -275,10 +296,9 @@ export class SyntaxBuilder implements Builder {
|
|
|
275
296
|
hintOffset: this.state.hintOffset - start,
|
|
276
297
|
hintExpected: hinted ? this.state.hintExpected : undefined
|
|
277
298
|
}
|
|
278
|
-
const
|
|
279
|
-
stack.
|
|
280
|
-
|
|
281
|
-
)
|
|
299
|
+
const green = build(kind, named, key, start, end, looked, stack, mark, a, undefined, diagnostics)
|
|
300
|
+
stack.length = mark
|
|
301
|
+
stack.push(new Child(green, start, end, undefined))
|
|
282
302
|
return a
|
|
283
303
|
}
|
|
284
304
|
|
|
@@ -292,7 +312,7 @@ export class SyntaxBuilder implements Builder {
|
|
|
292
312
|
if (this.state.deepest === undefined || failure.offset >= this.state.deepest.offset) this.state.deepest = failure
|
|
293
313
|
}
|
|
294
314
|
|
|
295
|
-
field(state: State<any>, step: (state: State<any>) =>
|
|
315
|
+
field<A>(state: State<any>, step: (state: State<any>) => A, name: string): A {
|
|
296
316
|
const stack = this.stack
|
|
297
317
|
const mark = stack.length
|
|
298
318
|
const a = step(state)
|
|
@@ -305,10 +325,11 @@ export class SyntaxBuilder implements Builder {
|
|
|
305
325
|
let i = stack.length
|
|
306
326
|
while (i > 0 && stack[i - 1].start >= start && stack[i - 1].end > start) i--
|
|
307
327
|
const end = this.state.pos
|
|
308
|
-
const green = build(errorKind, true, undefined, start, end, this.looked(), stack
|
|
328
|
+
const green = build(errorKind, true, undefined, start, end, this.looked(), stack, i, undefined, {
|
|
309
329
|
failure: rebase(failure, start),
|
|
310
330
|
mode
|
|
311
331
|
})
|
|
332
|
+
stack.length = i
|
|
312
333
|
stack.push(new Child(green, start, end, undefined))
|
|
313
334
|
}
|
|
314
335
|
|
|
@@ -323,13 +344,13 @@ export class SyntaxBuilder implements Builder {
|
|
|
323
344
|
if (fatal !== undefined) {
|
|
324
345
|
const last = Arr.isArrayNonEmpty(entries) ? Arr.lastNonEmpty(entries).end : 0
|
|
325
346
|
const start = Math.max(Math.min(errorStart, end), last)
|
|
326
|
-
const error = build(errorKind, true, undefined, start, end, end,
|
|
347
|
+
const error = build(errorKind, true, undefined, start, end, end, none, 0, undefined, {
|
|
327
348
|
failure: rebase(fatal.failure, start),
|
|
328
349
|
mode: fatal.mode
|
|
329
350
|
})
|
|
330
351
|
entries.push(new Child(error, start, end, undefined))
|
|
331
352
|
}
|
|
332
|
-
return build(kind, true, undefined, 0, end, Math.max(end, this.looked()), entries, value, undefined)
|
|
353
|
+
return build(kind, true, undefined, 0, end, Math.max(end, this.looked()), entries, 0, value, undefined)
|
|
333
354
|
}
|
|
334
355
|
|
|
335
356
|
/**
|
package/src/internal/text.ts
CHANGED
|
@@ -38,7 +38,18 @@ export const isEmptyUnits = (units: string | Uint8Array | ReadonlyArray<unknown>
|
|
|
38
38
|
|
|
39
39
|
/** @internal */
|
|
40
40
|
export const fastLimit = (enc: Encoding<any>): number =>
|
|
41
|
-
|
|
41
|
+
// SAFETY: the built-in encodings carry the internal `FastLimit` key; on any other encoding it reads `undefined`
|
|
42
|
+
(enc as Encoding<any> & { readonly [FastLimit]?: number })[FastLimit] ??
|
|
43
|
+
(enc.asciiCompatible && enc.unit === 1 ? 0x80 : 0)
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* `String.fromCharCode`, seen as taking its argument list through `apply` as
|
|
47
|
+
* any array-like (which `Function.prototype.apply` accepts), such as a view of
|
|
48
|
+
* bytes.
|
|
49
|
+
*/
|
|
50
|
+
export interface FromCharCodes {
|
|
51
|
+
readonly apply: (thisArg: null, codes: ArrayLike<number>) => string
|
|
52
|
+
}
|
|
42
53
|
|
|
43
54
|
const asciiChars: ReadonlyArray<string> = /*#__PURE__*/ Array.from({ length: 128 }, (_, i) => String.fromCharCode(i))
|
|
44
55
|
|
|
@@ -68,6 +79,7 @@ const decodeString = (s: string, i: number, end: number): number => {
|
|
|
68
79
|
* JavaScript strings: UTF-16 code units. Offsets are code-unit indices and
|
|
69
80
|
* slices are `String.prototype.slice`s, so no decoding is needed.
|
|
70
81
|
*/
|
|
82
|
+
// SAFETY: an `Encoding` with the internal `FastLimit` key, which its type doesn't declare
|
|
71
83
|
export const string: Encoding<string> = {
|
|
72
84
|
name: "UTF-16",
|
|
73
85
|
unit: 1,
|
|
@@ -112,9 +124,38 @@ const decodeUtf8 = (b: Uint8Array, i: number, end: number): number => {
|
|
|
112
124
|
return Invalid
|
|
113
125
|
}
|
|
114
126
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
127
|
+
const fromCharCode = String.fromCharCode
|
|
128
|
+
|
|
129
|
+
/** How many of the (up to eight) bytes from `i` are ASCII; at least one, as `b[i]` is. */
|
|
130
|
+
const asciiPrefix = (b: Uint8Array, i: number, end: number): number => {
|
|
131
|
+
const n = end - i < 8 ? end - i : 8
|
|
132
|
+
let k = 1
|
|
133
|
+
while (k < n && b[i + k] < 0x80) k++
|
|
134
|
+
return k
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** The `n` (1 to 8) ASCII bytes from `i`, as a string. */
|
|
138
|
+
const asciiText = (b: Uint8Array, i: number, n: number): string => {
|
|
139
|
+
if (n === 8) return fromCharCode(b[i], b[i + 1], b[i + 2], b[i + 3], b[i + 4], b[i + 5], b[i + 6], b[i + 7])
|
|
140
|
+
if (n === 1) return asciiChars[b[i]]
|
|
141
|
+
if (n === 2) return fromCharCode(b[i], b[i + 1])
|
|
142
|
+
if (n === 3) return fromCharCode(b[i], b[i + 1], b[i + 2])
|
|
143
|
+
if (n === 4) return fromCharCode(b[i], b[i + 1], b[i + 2], b[i + 3])
|
|
144
|
+
if (n === 5) return fromCharCode(b[i], b[i + 1], b[i + 2], b[i + 3], b[i + 4])
|
|
145
|
+
if (n === 6) return fromCharCode(b[i], b[i + 1], b[i + 2], b[i + 3], b[i + 4], b[i + 5])
|
|
146
|
+
return fromCharCode(b[i], b[i + 1], b[i + 2], b[i + 3], b[i + 4], b[i + 5], b[i + 6])
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** The longest UTF-16 range, in bytes, built by hand rather than by a `TextDecoder` call. */
|
|
150
|
+
const ShortUtf16 = 48
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* The longest UTF-8 range built by hand: built eight ASCII bytes at a
|
|
154
|
+
* time, shorter ones are faster than a `TextDecoder` call, which costs
|
|
155
|
+
* about 120 ns whatever the length. Raising it past about 50 would also
|
|
156
|
+
* keep typical strings from {@link utf8Text}'s ASCII runs.
|
|
157
|
+
*/
|
|
158
|
+
export const ShortText = 40
|
|
118
159
|
|
|
119
160
|
const utf8Decoder = /*#__PURE__*/ new TextDecoder("utf-8", { ignoreBOM: true })
|
|
120
161
|
|
|
@@ -128,6 +169,46 @@ const decodeRange = (decoder: TextDecoder, b: Uint8Array, start: number, end: nu
|
|
|
128
169
|
}
|
|
129
170
|
const utf8Encoder = /*#__PURE__*/ new TextEncoder()
|
|
130
171
|
|
|
172
|
+
/** A run of ASCII bytes, `[start, end)` of a state's input, and its text. */
|
|
173
|
+
export interface AsciiRun {
|
|
174
|
+
readonly start: number
|
|
175
|
+
readonly end: number
|
|
176
|
+
readonly text: string
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** How far past a string {@link utf8Text} looks for the end of its ASCII run. */
|
|
180
|
+
const RunWindow = 16384
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Decodes `[start, end)` of a state's UTF-8 input, as `utf8.text` does.
|
|
184
|
+
* Each `TextDecoder` call has a fixed cost of about 120 ns, more than
|
|
185
|
+
* slicing a string, so a long string inside a run of ASCII bytes (where a
|
|
186
|
+
* byte offset is a character offset) is sliced from the text of the whole
|
|
187
|
+
* run, decoded once and kept on the state; short strings, and those the run
|
|
188
|
+
* doesn't cover, are decoded as `utf8.text` decodes them.
|
|
189
|
+
*/
|
|
190
|
+
export const utf8Text = (s: State<any>, start: number, end: number): string => {
|
|
191
|
+
// SAFETY: called only by `Text.utf8` parsers, whose input is bytes
|
|
192
|
+
const b = s.input as Uint8Array
|
|
193
|
+
// `textIn` and the JIT check this themselves before calling
|
|
194
|
+
if (end - start <= ShortText) return utf8.text(b, start, end)
|
|
195
|
+
const run = s.asciiRun
|
|
196
|
+
if (run !== undefined && start >= run.start && end <= run.end)
|
|
197
|
+
return run.text.slice(start - run.start, end - run.start)
|
|
198
|
+
let e = start
|
|
199
|
+
const limit = Math.min(s.end, end + RunWindow)
|
|
200
|
+
while (e < limit && b[e] < 0x80) e++
|
|
201
|
+
// Only worth it when the run holds more than this string
|
|
202
|
+
if (e < end || e - start < 4 * (end - start)) return decodeRange(utf8Decoder, b, start, end)
|
|
203
|
+
const text = decodeRange(utf8Decoder, b, start, e)
|
|
204
|
+
s.asciiRun = { start, end: e, text }
|
|
205
|
+
return text.slice(0, end - start)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** Decodes `[start, end)` of a state's input in `enc`, through {@link utf8Text} for UTF-8. */
|
|
209
|
+
export const textIn = (enc: Encoding<any>, s: State<any>, start: number, end: number): string =>
|
|
210
|
+
enc === utf8 && end - start > ShortText ? utf8Text(s, start, end) : enc.text(s.input, start, end)
|
|
211
|
+
|
|
131
212
|
/** UTF-8 bytes. */
|
|
132
213
|
// SAFETY: an `Encoding` with the internal `FastLimit` key, which its type doesn't declare
|
|
133
214
|
export const utf8: Encoding<Uint8Array> = {
|
|
@@ -138,13 +219,16 @@ export const utf8: Encoding<Uint8Array> = {
|
|
|
138
219
|
width: (cp) => (cp < 0x80 ? 1 : cp < 0x800 ? 2 : cp < 0x10000 ? 3 : 4),
|
|
139
220
|
encode: (text) => utf8Encoder.encode(text),
|
|
140
221
|
text: (b, start, end) => {
|
|
141
|
-
if (end - start >
|
|
222
|
+
if (end - start > ShortText) return decodeRange(utf8Decoder, b, start, end)
|
|
142
223
|
let out = ""
|
|
143
224
|
for (let i = start; i < end;) {
|
|
144
225
|
const c = b[i]
|
|
145
226
|
if (c < 0x80) {
|
|
146
|
-
|
|
147
|
-
|
|
227
|
+
// Up to eight ASCII bytes in one call: a string a character at a
|
|
228
|
+
// time costs about 8 ns a character, eight in one call about 20 ns
|
|
229
|
+
const n = asciiPrefix(b, i, end)
|
|
230
|
+
out += asciiText(b, i, n)
|
|
231
|
+
i += n
|
|
148
232
|
} else {
|
|
149
233
|
const cp = decodeUtf8(b, i, end)
|
|
150
234
|
out += cp < 0x10000 ? String.fromCharCode(cp) : String.fromCodePoint(cp)
|
|
@@ -187,7 +271,7 @@ const makeUtf16 = (littleEndian: boolean): Encoding<Uint8Array> => {
|
|
|
187
271
|
return out
|
|
188
272
|
},
|
|
189
273
|
text: (b, start, end) => {
|
|
190
|
-
if (end - start <=
|
|
274
|
+
if (end - start <= ShortUtf16) {
|
|
191
275
|
// The range is well formed, so surrogate pairs can be copied unit by unit
|
|
192
276
|
let out = ""
|
|
193
277
|
for (let i = start; i < end; i += 2) out += String.fromCharCode(b[i + lo] | (b[i + hi] << 8))
|
|
@@ -207,6 +291,7 @@ export const utf16le: Encoding<Uint8Array> = /*#__PURE__*/ makeUtf16(true)
|
|
|
207
291
|
export const utf16be: Encoding<Uint8Array> = /*#__PURE__*/ makeUtf16(false)
|
|
208
292
|
|
|
209
293
|
/** ISO-8859-1: every byte is the code point of the same value. */
|
|
294
|
+
// SAFETY: an `Encoding` with the internal `FastLimit` key, which its type doesn't declare
|
|
210
295
|
export const latin1: Encoding<Uint8Array> = {
|
|
211
296
|
name: "ISO-8859-1",
|
|
212
297
|
unit: 1,
|
|
@@ -225,7 +310,8 @@ export const latin1: Encoding<Uint8Array> = {
|
|
|
225
310
|
text: (b, start, end) => {
|
|
226
311
|
let out = ""
|
|
227
312
|
for (let i = start; i < end; i += 8192) {
|
|
228
|
-
|
|
313
|
+
// SAFETY: `apply` takes any array-like argument list, so each byte of the view is passed as a char code
|
|
314
|
+
out += (String.fromCharCode as FromCharCodes).apply(null, b.subarray(i, Math.min(end, i + 8192)))
|
|
229
315
|
}
|
|
230
316
|
return out
|
|
231
317
|
},
|
|
@@ -302,6 +388,7 @@ export const scan = (
|
|
|
302
388
|
let count = 0
|
|
303
389
|
let stop = 0
|
|
304
390
|
if (state.inputKind === 0) {
|
|
391
|
+
// SAFETY: `inputKind` 0 is set only for a string input
|
|
305
392
|
const s = state.input as string
|
|
306
393
|
while (count < max) {
|
|
307
394
|
if (i >= end) {
|
|
@@ -324,6 +411,7 @@ export const scan = (
|
|
|
324
411
|
count++
|
|
325
412
|
}
|
|
326
413
|
} else {
|
|
414
|
+
// SAFETY: an encoding's input is a string or a `Uint8Array`, and `inputKind` is not 0, so a `Uint8Array`
|
|
327
415
|
const b = state.input as Uint8Array
|
|
328
416
|
while (count < max) {
|
|
329
417
|
if (i >= end) {
|