@thomasfosterau/effect-parse 0.1.0 → 0.2.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 +247 -230
- package/dist/Ascii.d.ts +32 -53
- package/dist/Ascii.d.ts.map +1 -1
- package/dist/Ascii.js +576 -571
- package/dist/Ascii.js.map +1 -1
- package/dist/Binary.d.ts +6 -29
- package/dist/Binary.d.ts.map +1 -1
- package/dist/Binary.js +129 -116
- package/dist/Binary.js.map +1 -1
- package/dist/Bits.d.ts +4 -2
- package/dist/Bits.d.ts.map +1 -1
- package/dist/Bits.js +121 -123
- package/dist/Bits.js.map +1 -1
- package/dist/Compiler.d.ts +39 -0
- package/dist/Compiler.d.ts.map +1 -0
- package/dist/Compiler.js +48 -0
- package/dist/Compiler.js.map +1 -0
- package/dist/Encoding.d.ts +29 -46
- package/dist/Encoding.d.ts.map +1 -1
- package/dist/Encoding.js +76 -75
- package/dist/Encoding.js.map +1 -1
- package/dist/Expression.d.ts +15 -49
- package/dist/Expression.d.ts.map +1 -1
- package/dist/Expression.js +143 -131
- package/dist/Expression.js.map +1 -1
- package/dist/Input.d.ts +206 -177
- package/dist/Input.d.ts.map +1 -1
- package/dist/Input.js +317 -299
- package/dist/Input.js.map +1 -1
- package/dist/Nom.d.ts +44 -84
- package/dist/Nom.d.ts.map +1 -1
- package/dist/Nom.js +423 -453
- package/dist/Nom.js.map +1 -1
- package/dist/ParseError.d.ts +54 -49
- package/dist/ParseError.d.ts.map +1 -1
- package/dist/ParseError.js +233 -275
- package/dist/ParseError.js.map +1 -1
- package/dist/Parser.d.ts +345 -313
- package/dist/Parser.d.ts.map +1 -1
- package/dist/Parser.js +2010 -1802
- package/dist/Parser.js.map +1 -1
- package/dist/ParserSchema.d.ts +8 -7
- package/dist/ParserSchema.d.ts.map +1 -1
- package/dist/ParserSchema.js +49 -49
- package/dist/ParserSchema.js.map +1 -1
- package/dist/ParserStream.d.ts +26 -20
- package/dist/ParserStream.d.ts.map +1 -1
- package/dist/ParserStream.js +267 -255
- package/dist/ParserStream.js.map +1 -1
- package/dist/Syntax.d.ts +401 -0
- package/dist/Syntax.d.ts.map +1 -0
- package/dist/Syntax.js +717 -0
- package/dist/Syntax.js.map +1 -0
- package/dist/Text.d.ts +98 -134
- package/dist/Text.d.ts.map +1 -1
- package/dist/Text.js +534 -496
- package/dist/Text.js.map +1 -1
- package/dist/Token.d.ts +60 -42
- package/dist/Token.d.ts.map +1 -1
- package/dist/Token.js +331 -265
- package/dist/Token.js.map +1 -1
- package/dist/index.d.ts +16 -17
- package/dist/index.js +16 -17
- package/dist/internal/compiler/closure.d.ts +14 -16
- package/dist/internal/compiler/closure.d.ts.map +1 -1
- package/dist/internal/compiler/closure.js +1458 -1637
- package/dist/internal/compiler/closure.js.map +1 -1
- package/dist/internal/compiler/entries.d.ts +11 -11
- package/dist/internal/compiler/entries.d.ts.map +1 -1
- package/dist/internal/compiler/entries.js +36 -26
- package/dist/internal/compiler/entries.js.map +1 -1
- package/dist/internal/compiler/first.d.ts +19 -19
- package/dist/internal/compiler/first.d.ts.map +1 -1
- package/dist/internal/compiler/first.js +251 -227
- package/dist/internal/compiler/first.js.map +1 -1
- package/dist/internal/compiler/jit.d.ts +6 -17
- package/dist/internal/compiler/jit.d.ts.map +1 -1
- package/dist/internal/compiler/jit.js +860 -988
- package/dist/internal/compiler/jit.js.map +1 -1
- package/dist/internal/compiler/registry.d.ts +20 -21
- package/dist/internal/compiler/registry.d.ts.map +1 -1
- package/dist/internal/compiler/registry.js +21 -41
- package/dist/internal/compiler/registry.js.map +1 -1
- package/dist/internal/compiler/sets.d.ts +9 -13
- package/dist/internal/compiler/sets.d.ts.map +1 -1
- package/dist/internal/compiler/sets.js +76 -73
- package/dist/internal/compiler/sets.js.map +1 -1
- package/dist/internal/entries.d.ts +3 -3
- package/dist/internal/entries.d.ts.map +1 -1
- package/dist/internal/entries.js +38 -30
- package/dist/internal/entries.js.map +1 -1
- package/dist/internal/node.d.ts +177 -173
- package/dist/internal/node.d.ts.map +1 -1
- package/dist/internal/node.js +77 -66
- package/dist/internal/node.js.map +1 -1
- package/dist/internal/numbers.d.ts +7 -8
- package/dist/internal/numbers.d.ts.map +1 -1
- package/dist/internal/numbers.js +246 -276
- package/dist/internal/numbers.js.map +1 -1
- package/dist/internal/resume.d.ts +6 -24
- package/dist/internal/resume.d.ts.map +1 -1
- package/dist/internal/resume.js +30 -29
- package/dist/internal/resume.js.map +1 -1
- package/dist/internal/run.d.ts +15 -0
- package/dist/internal/run.d.ts.map +1 -0
- package/dist/internal/run.js +36 -0
- package/dist/internal/run.js.map +1 -0
- package/dist/internal/stack.d.ts +16 -0
- package/dist/internal/stack.d.ts.map +1 -0
- package/dist/internal/stack.js +26 -0
- package/dist/internal/stack.js.map +1 -0
- package/dist/internal/syntax.d.ts +146 -0
- package/dist/internal/syntax.d.ts.map +1 -0
- package/dist/internal/syntax.js +305 -0
- package/dist/internal/syntax.js.map +1 -0
- package/dist/internal/text.d.ts +10 -2
- package/dist/internal/text.d.ts.map +1 -1
- package/dist/internal/text.js +295 -345
- package/dist/internal/text.js.map +1 -1
- package/dist/internal/tokens.d.ts +25 -30
- package/dist/internal/tokens.d.ts.map +1 -1
- package/dist/internal/tokens.js +369 -363
- package/dist/internal/tokens.js.map +1 -1
- package/package.json +42 -28
- package/src/Ascii.ts +278 -187
- package/src/Binary.ts +150 -44
- package/src/Bits.ts +6 -3
- package/src/Compiler.ts +98 -0
- package/src/Expression.ts +10 -5
- package/src/Input.ts +106 -28
- package/src/Nom.ts +4 -3
- package/src/ParseError.ts +48 -51
- package/src/Parser.ts +1163 -795
- package/src/ParserStream.ts +104 -80
- package/src/Syntax.ts +900 -0
- package/src/Text.ts +272 -187
- package/src/Token.ts +180 -87
- package/src/index.ts +2 -0
- package/src/internal/compiler/closure.ts +121 -88
- package/src/internal/compiler/entries.ts +21 -8
- package/src/internal/compiler/first.ts +17 -15
- package/src/internal/compiler/jit.ts +360 -157
- package/src/internal/compiler/registry.ts +12 -33
- package/src/internal/compiler/sets.ts +17 -11
- package/src/internal/entries.ts +4 -2
- package/src/internal/node.ts +51 -37
- package/src/internal/numbers.ts +22 -20
- package/src/internal/resume.ts +8 -4
- package/src/internal/run.ts +41 -0
- package/src/internal/stack.ts +36 -0
- package/src/internal/syntax.ts +421 -0
- package/src/internal/text.ts +32 -25
- package/src/internal/tokens.ts +96 -51
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The tree builder behind the `Syntax` module (see `docs/DESIGN.md`,
|
|
3
|
+
* "Syntax trees").
|
|
4
|
+
*
|
|
5
|
+
* Trees are stored as *green* nodes, as in Roslyn and rust-analyzer: a green
|
|
6
|
+
* node knows its kind, its width and its children's offsets relative to its
|
|
7
|
+
* own start, but not where it is in the input or who its parent is. A subtree
|
|
8
|
+
* that an edit doesn't touch can therefore be shared, unchanged, by the tree
|
|
9
|
+
* before the edit and the tree after it. `Syntax.SyntaxNode` is the *red*
|
|
10
|
+
* view: a green node, its absolute start and its parent, built lazily while
|
|
11
|
+
* navigating.
|
|
12
|
+
*
|
|
13
|
+
* While parsing, completed nodes wait on a stack until the node around them
|
|
14
|
+
* completes and adopts them. Nodes built by an attempt that is later
|
|
15
|
+
* abandoned must not end up in the tree. Every combinator that abandons an
|
|
16
|
+
* attempt moves the cursor back to (or before) where the attempt started,
|
|
17
|
+
* and every node has a positive width, so the nodes of an abandoned attempt
|
|
18
|
+
* are exactly those that end after the position the cursor moves back to:
|
|
19
|
+
* the `pos` setter of {@link SyntaxState} drops them. This also covers
|
|
20
|
+
* lookahead (`peek`, `not`) and custom parsers written with `Parser.make`.
|
|
21
|
+
*
|
|
22
|
+
* For incremental parsing, every node records how far its parse looked: the
|
|
23
|
+
* furthest position the cursor reached and the furthest failure recorded
|
|
24
|
+
* while it was being parsed (its *lookahead*). A node from the previous tree
|
|
25
|
+
* is reused at a position when it was built by the same `Syntax.node`, and
|
|
26
|
+
* the input it looked at, plus a margin for the tokens that read a little
|
|
27
|
+
* past where they stop, is unchanged.
|
|
28
|
+
*/
|
|
29
|
+
import * as Predicate from "effect/Predicate"
|
|
30
|
+
import type * as Context from "effect/Context"
|
|
31
|
+
import type { Encoding } from "../Encoding.js"
|
|
32
|
+
import type { ErrMode, Input, Source } from "../Input.js"
|
|
33
|
+
import { Fail, Failure, State } from "../Input.js"
|
|
34
|
+
|
|
35
|
+
/** What a failure inside an `ERROR` node was. Offsets are relative to the node. */
|
|
36
|
+
export interface ErrorInfo {
|
|
37
|
+
readonly failure: Failure
|
|
38
|
+
readonly mode: ErrMode
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** A position-independent syntax tree node. */
|
|
42
|
+
export class Green {
|
|
43
|
+
constructor(
|
|
44
|
+
readonly kind: string,
|
|
45
|
+
readonly named: boolean,
|
|
46
|
+
/** The `Syntax.node` that built it; `undefined` for roots and errors, which are never reused alone. */
|
|
47
|
+
readonly key: object | undefined,
|
|
48
|
+
readonly width: number,
|
|
49
|
+
/** How far its parse looked, relative to its start (at least `width`). */
|
|
50
|
+
readonly lookahead: number,
|
|
51
|
+
readonly children: ReadonlyArray<Green>,
|
|
52
|
+
/** The children's starts, relative to this node's start. */
|
|
53
|
+
readonly offsets: ReadonlyArray<number>,
|
|
54
|
+
/** The children's field names, when any child has one. */
|
|
55
|
+
readonly fields: ReadonlyArray<string | undefined> | undefined,
|
|
56
|
+
readonly value: unknown,
|
|
57
|
+
/** Set on `ERROR` nodes. */
|
|
58
|
+
readonly error: ErrorInfo | undefined,
|
|
59
|
+
/** Whether this node or a descendant is an `ERROR` node. */
|
|
60
|
+
readonly hasError: boolean,
|
|
61
|
+
/** What its parse left behind for error reporting, replayed when it is reused. */
|
|
62
|
+
readonly diagnostics: Diagnostics | undefined
|
|
63
|
+
) {}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The error-reporting state a node's parse leaves behind (see
|
|
68
|
+
* `State.deepest` and `State.setHint`). A reused node replays it, so that
|
|
69
|
+
* a reparse reports the errors a fresh parse does.
|
|
70
|
+
*/
|
|
71
|
+
export interface Diagnostics {
|
|
72
|
+
/** The furthest failure, at its offset in the parse that built the node... */
|
|
73
|
+
readonly deepest: Failure | undefined
|
|
74
|
+
/** ...which started the node at `base`. */
|
|
75
|
+
readonly base: number
|
|
76
|
+
/** Relative to the node's start. */
|
|
77
|
+
readonly hintOffset: number
|
|
78
|
+
readonly hintExpected: ReadonlyArray<string> | undefined
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** A completed node waiting for its parent, at an absolute position. */
|
|
82
|
+
class Child {
|
|
83
|
+
constructor(
|
|
84
|
+
readonly green: Green,
|
|
85
|
+
readonly start: number,
|
|
86
|
+
readonly end: number,
|
|
87
|
+
public field: string | undefined
|
|
88
|
+
) {}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export const errorKind = "ERROR"
|
|
92
|
+
|
|
93
|
+
const rebase = (f: Failure, start: number): Failure =>
|
|
94
|
+
new Failure(f.offset - start, f.kind, f.expected, f.context, f.hasCause, f.cause, f.message, f.needed)
|
|
95
|
+
|
|
96
|
+
/** Moves a failure stored relative to a node back to absolute offsets. */
|
|
97
|
+
export const absolute = (f: Failure, start: number): Failure => rebase(f, -start)
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Builds a green node over `[start, end)` from the children waiting on the
|
|
101
|
+
* stack.
|
|
102
|
+
*/
|
|
103
|
+
const build = (
|
|
104
|
+
kind: string,
|
|
105
|
+
named: boolean,
|
|
106
|
+
key: object | undefined,
|
|
107
|
+
start: number,
|
|
108
|
+
end: number,
|
|
109
|
+
lookahead: number,
|
|
110
|
+
entries: ReadonlyArray<Child>,
|
|
111
|
+
value: unknown,
|
|
112
|
+
error: ErrorInfo | undefined,
|
|
113
|
+
diagnostics?: Diagnostics
|
|
114
|
+
): Green => {
|
|
115
|
+
const n = entries.length
|
|
116
|
+
const children = new Array<Green>(n)
|
|
117
|
+
const offsets = new Array<number>(n)
|
|
118
|
+
let fields: Array<string | undefined> | undefined
|
|
119
|
+
let hasError = error !== undefined
|
|
120
|
+
for (let i = 0; i < n; i++) {
|
|
121
|
+
const e = entries[i]
|
|
122
|
+
children[i] = e.green
|
|
123
|
+
offsets[i] = e.start - start
|
|
124
|
+
if (e.green.hasError) hasError = true
|
|
125
|
+
if (e.field !== undefined) (fields ??= new Array(n).fill(undefined))[i] = e.field
|
|
126
|
+
}
|
|
127
|
+
const width = end - start
|
|
128
|
+
const looked = Math.max(lookahead, end) - start
|
|
129
|
+
return new Green(kind, named, key, width, looked, children, offsets, fields, value, error, hasError, diagnostics)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* A stretch of the new input copied unchanged from the previous one:
|
|
134
|
+
* `[newStart, newStart + length)` is `[oldStart, oldStart + length)`.
|
|
135
|
+
*/
|
|
136
|
+
export interface Segment {
|
|
137
|
+
readonly newStart: number
|
|
138
|
+
readonly oldStart: number
|
|
139
|
+
readonly length: number
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** What a reparse may reuse. */
|
|
143
|
+
export interface Previous {
|
|
144
|
+
readonly root: Green
|
|
145
|
+
readonly oldLength: number
|
|
146
|
+
readonly newLength: number
|
|
147
|
+
readonly segments: ReadonlyArray<Segment>
|
|
148
|
+
readonly input: Input
|
|
149
|
+
/** How far past its lookahead a parser may have read, besides a token at the lookahead. */
|
|
150
|
+
readonly extra: number
|
|
151
|
+
readonly encoding: Encoding<any> | undefined
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** The hooks that `Syntax` combinators and `Parser.resumeAfter`/`retryAfter` call. */
|
|
155
|
+
export interface Builder {
|
|
156
|
+
node(state: State<any>, step: (state: State<any>) => unknown, key: object, kind: string, named: boolean): unknown
|
|
157
|
+
field(state: State<any>, step: (state: State<any>) => unknown, name: string): unknown
|
|
158
|
+
/** After error recovery: `[start, pos)` was skipped after `failure`. */
|
|
159
|
+
skipped(start: number, failure: Failure, mode: ErrMode): void
|
|
160
|
+
/**
|
|
161
|
+
* Called by token parsers that read input up to `end` (exclusive) beyond
|
|
162
|
+
* where they leave the cursor or record a failure.
|
|
163
|
+
*/
|
|
164
|
+
read(end: number): void
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** The parse state of a `Syntax` parse. */
|
|
168
|
+
export class SyntaxState<I extends Input = Input> extends State<I> implements Builder {
|
|
169
|
+
// Declared, not defined: `State`'s constructor sets `pos` through the
|
|
170
|
+
// accessor below before these could be initialized
|
|
171
|
+
declare cursor: number
|
|
172
|
+
/** The furthest position the cursor reached, or failure recorded, since the innermost open node started. */
|
|
173
|
+
declare highWater: number
|
|
174
|
+
/** Completed nodes waiting for their parent, in input order. */
|
|
175
|
+
declare stack: Array<Child>
|
|
176
|
+
declare previous: Previous | undefined
|
|
177
|
+
/** How many nodes were reused from the previous tree. */
|
|
178
|
+
declare reused: number
|
|
179
|
+
/** Where {@link find} found the last node it found. */
|
|
180
|
+
declare lastParent: Green | undefined
|
|
181
|
+
declare lastParentStart: number
|
|
182
|
+
|
|
183
|
+
static {
|
|
184
|
+
Object.defineProperty(this.prototype, "pos", {
|
|
185
|
+
get(this: SyntaxState) {
|
|
186
|
+
return this.cursor
|
|
187
|
+
},
|
|
188
|
+
set(this: SyntaxState, p: number) {
|
|
189
|
+
if (p < this.cursor) {
|
|
190
|
+
// An attempt was abandoned (or a lookahead finished): drop its nodes
|
|
191
|
+
const stack = this.stack
|
|
192
|
+
while (stack.length > 0 && stack[stack.length - 1].end > p) stack.pop()
|
|
193
|
+
}
|
|
194
|
+
if (p > this.highWater) this.highWater = p
|
|
195
|
+
this.cursor = p
|
|
196
|
+
},
|
|
197
|
+
configurable: true,
|
|
198
|
+
enumerable: true
|
|
199
|
+
})
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
constructor(
|
|
203
|
+
source: I | Source<I>,
|
|
204
|
+
options: {
|
|
205
|
+
readonly context?: Context.Context<never> | undefined
|
|
206
|
+
},
|
|
207
|
+
previous: Previous | undefined
|
|
208
|
+
) {
|
|
209
|
+
super(source, { context: options.context })
|
|
210
|
+
this.highWater = this.cursor
|
|
211
|
+
this.stack = []
|
|
212
|
+
this.previous = previous
|
|
213
|
+
this.reused = 0
|
|
214
|
+
this.lastParent = undefined
|
|
215
|
+
this.lastParentStart = 0
|
|
216
|
+
this.syntax = this
|
|
217
|
+
// Error recovery is always on: like Tree-sitter, a syntax parse always produces a tree
|
|
218
|
+
this.recovered = []
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
override failWith(mode: ErrMode, failure: Failure): Fail {
|
|
222
|
+
if (failure.offset > this.highWater) this.highWater = failure.offset
|
|
223
|
+
return super.failWith(mode, failure)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
override setHint(offset: number, expected: ReadonlyArray<string>): void {
|
|
227
|
+
if (offset > this.highWater) this.highWater = offset
|
|
228
|
+
super.setHint(offset, expected)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
node(state: State<any>, step: (state: State<any>) => unknown, key: object, kind: string, named: boolean): unknown {
|
|
232
|
+
const start = this.cursor
|
|
233
|
+
const stack = this.stack
|
|
234
|
+
if (this.previous !== undefined) {
|
|
235
|
+
const green = this.reusable(this.previous, key, start)
|
|
236
|
+
if (green !== undefined) {
|
|
237
|
+
this.reused++
|
|
238
|
+
const end = start + green.width
|
|
239
|
+
state.pos = end
|
|
240
|
+
const looked = start + green.lookahead
|
|
241
|
+
if (looked > this.highWater) this.highWater = looked
|
|
242
|
+
const d = green.diagnostics
|
|
243
|
+
if (d !== undefined) {
|
|
244
|
+
if (d.deepest !== undefined) this.deepen(start === d.base ? d.deepest : rebase(d.deepest, d.base - start))
|
|
245
|
+
if (d.hintExpected !== undefined) this.setHint(start + d.hintOffset, d.hintExpected)
|
|
246
|
+
}
|
|
247
|
+
stack.push(new Child(green, start, end, undefined))
|
|
248
|
+
return green.value
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
const mark = stack.length
|
|
252
|
+
const outer = this.highWater
|
|
253
|
+
const outerDeepest = this.deepest
|
|
254
|
+
const hintOffset = this.hintOffset
|
|
255
|
+
const hintExpected = this.hintExpected
|
|
256
|
+
this.highWater = start
|
|
257
|
+
// Collect the furthest failure of this node alone, then merge it as `failWith` would have
|
|
258
|
+
this.deepest = undefined
|
|
259
|
+
const a = step(state)
|
|
260
|
+
const looked = this.highWater
|
|
261
|
+
if (outer > looked) this.highWater = outer
|
|
262
|
+
const deepest = this.deepest as Failure | undefined
|
|
263
|
+
this.deepest = outerDeepest
|
|
264
|
+
if (deepest !== undefined) this.deepen(deepest)
|
|
265
|
+
if (a === Fail) {
|
|
266
|
+
if (stack.length > mark) stack.length = mark
|
|
267
|
+
return a
|
|
268
|
+
}
|
|
269
|
+
const end = this.cursor
|
|
270
|
+
// Like Tree-sitter's rules, nodes are never empty: an empty match adds nothing to the tree
|
|
271
|
+
if (end === start) return a
|
|
272
|
+
const hinted = this.hintOffset !== hintOffset || this.hintExpected !== hintExpected
|
|
273
|
+
const diagnostics: Diagnostics | undefined =
|
|
274
|
+
deepest === undefined && !hinted
|
|
275
|
+
? undefined
|
|
276
|
+
: {
|
|
277
|
+
deepest,
|
|
278
|
+
base: start,
|
|
279
|
+
hintOffset: this.hintOffset - start,
|
|
280
|
+
hintExpected: hinted ? this.hintExpected : undefined
|
|
281
|
+
}
|
|
282
|
+
const entries = stack.splice(mark)
|
|
283
|
+
stack.push(
|
|
284
|
+
new Child(build(kind, named, key, start, end, looked, entries, a, undefined, diagnostics), start, end, undefined)
|
|
285
|
+
)
|
|
286
|
+
return a
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
read(end: number): void {
|
|
290
|
+
// `highWater` is the last position looked at, `end` is exclusive
|
|
291
|
+
if (end - 1 > this.highWater) this.highWater = end - 1
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/** Records `failure` as the furthest failure if it is, as `failWith` does. */
|
|
295
|
+
private deepen(failure: Failure): void {
|
|
296
|
+
if (this.deepest === undefined || failure.offset >= this.deepest.offset) this.deepest = failure
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
field(state: State<any>, step: (state: State<any>) => unknown, name: string): unknown {
|
|
300
|
+
const stack = this.stack
|
|
301
|
+
const mark = stack.length
|
|
302
|
+
const a = step(state)
|
|
303
|
+
if (a !== Fail) for (let i = mark; i < stack.length; i++) stack[i].field = name
|
|
304
|
+
return a
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
skipped(start: number, failure: Failure, mode: ErrMode): void {
|
|
308
|
+
const stack = this.stack
|
|
309
|
+
let i = stack.length
|
|
310
|
+
while (i > 0 && stack[i - 1].start >= start && stack[i - 1].end > start) i--
|
|
311
|
+
const end = this.cursor
|
|
312
|
+
const green = build(errorKind, true, undefined, start, end, this.highWater, stack.splice(i), undefined, {
|
|
313
|
+
failure: rebase(failure, start),
|
|
314
|
+
mode
|
|
315
|
+
})
|
|
316
|
+
stack.push(new Child(green, start, end, undefined))
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Builds the root over the whole input from the nodes left on the stack.
|
|
321
|
+
* When the parse failed, or stopped before the end, the rest of the input
|
|
322
|
+
* becomes an `ERROR` node.
|
|
323
|
+
*/
|
|
324
|
+
finish(kind: string, value: unknown, fatal: ErrorInfo | undefined, errorStart: number): Green {
|
|
325
|
+
const entries = this.stack.splice(0)
|
|
326
|
+
const end = this.end
|
|
327
|
+
if (fatal !== undefined) {
|
|
328
|
+
const last = entries.length > 0 ? entries[entries.length - 1].end : 0
|
|
329
|
+
const start = Math.max(Math.min(errorStart, end), last)
|
|
330
|
+
const error = build(errorKind, true, undefined, start, end, end, [], undefined, {
|
|
331
|
+
failure: rebase(fatal.failure, start),
|
|
332
|
+
mode: fatal.mode
|
|
333
|
+
})
|
|
334
|
+
entries.push(new Child(error, start, end, undefined))
|
|
335
|
+
}
|
|
336
|
+
return build(kind, true, undefined, 0, end, Math.max(end, this.highWater), entries, value, undefined)
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* The outermost node of the previous tree built by `key` that starts at
|
|
341
|
+
* `offset`. Reused nodes are mostly siblings, found one after the other,
|
|
342
|
+
* so the search starts from the parent of the last one found when
|
|
343
|
+
* `offset` lies inside it: no ancestor of that parent can start there.
|
|
344
|
+
*/
|
|
345
|
+
private find(root: Green, offset: number, key: object): Green | undefined {
|
|
346
|
+
let node = root
|
|
347
|
+
let start = 0
|
|
348
|
+
const last = this.lastParent
|
|
349
|
+
if (last !== undefined && offset > this.lastParentStart && offset < this.lastParentStart + last.width) {
|
|
350
|
+
node = last
|
|
351
|
+
start = this.lastParentStart
|
|
352
|
+
}
|
|
353
|
+
while (true) {
|
|
354
|
+
const offsets = node.offsets
|
|
355
|
+
// The last child starting at or before `offset`
|
|
356
|
+
let lo = 0
|
|
357
|
+
let hi = offsets.length - 1
|
|
358
|
+
let found = -1
|
|
359
|
+
while (lo <= hi) {
|
|
360
|
+
const mid = (lo + hi) >> 1
|
|
361
|
+
if (start + offsets[mid] <= offset) {
|
|
362
|
+
found = mid
|
|
363
|
+
lo = mid + 1
|
|
364
|
+
} else {
|
|
365
|
+
hi = mid - 1
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
if (found < 0) return undefined
|
|
369
|
+
const child = node.children[found]
|
|
370
|
+
const childStart = start + offsets[found]
|
|
371
|
+
if (offset >= childStart + child.width) return undefined
|
|
372
|
+
if (childStart === offset && child.key === key) {
|
|
373
|
+
this.lastParent = node
|
|
374
|
+
this.lastParentStart = start
|
|
375
|
+
return child
|
|
376
|
+
}
|
|
377
|
+
node = child
|
|
378
|
+
start = childStart
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/** The node of the previous tree built by `key` at `pos` (in the new input), if it can be reused. */
|
|
383
|
+
private reusable(previous: Previous, key: object, pos: number): Green | undefined {
|
|
384
|
+
const segments = previous.segments
|
|
385
|
+
// The segment containing `pos`
|
|
386
|
+
let lo = 0
|
|
387
|
+
let hi = segments.length - 1
|
|
388
|
+
while (lo <= hi) {
|
|
389
|
+
const mid = (lo + hi) >> 1
|
|
390
|
+
const seg = segments[mid]
|
|
391
|
+
if (pos < seg.newStart) hi = mid - 1
|
|
392
|
+
else if (pos >= seg.newStart + seg.length) lo = mid + 1
|
|
393
|
+
else {
|
|
394
|
+
const old = seg.oldStart + (pos - seg.newStart)
|
|
395
|
+
const green = this.find(previous.root, old, key)
|
|
396
|
+
if (green === undefined) return undefined
|
|
397
|
+
const segEnd = seg.oldStart + seg.length
|
|
398
|
+
// The input it looked at must be unchanged; past the end of both inputs counts as unchanged
|
|
399
|
+
if (old + green.lookahead + tokenWidth(previous.input, old + green.lookahead) + previous.extra <= segEnd) {
|
|
400
|
+
return green
|
|
401
|
+
}
|
|
402
|
+
if (segEnd === previous.oldLength && seg.newStart + seg.length === previous.newLength) return green
|
|
403
|
+
return undefined
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
return undefined
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* How many units a token parser may read at `offset`, where a parse
|
|
412
|
+
* stopped looking: one code point. Of a string, that is two units at a high
|
|
413
|
+
* surrogate. Of bytes in an unknown encoding, up to four.
|
|
414
|
+
*/
|
|
415
|
+
const tokenWidth = (input: Input, offset: number): number => {
|
|
416
|
+
if (Predicate.isString(input)) {
|
|
417
|
+
const c = input.charCodeAt(offset)
|
|
418
|
+
return c >= 0xd800 && c <= 0xdbff ? 2 : 1
|
|
419
|
+
}
|
|
420
|
+
return input instanceof Uint8Array ? 4 : 1
|
|
421
|
+
}
|
package/src/internal/text.ts
CHANGED
|
@@ -20,17 +20,24 @@ export const End = -3
|
|
|
20
20
|
* Units below this value decode to themselves with width 1, so hot loops can
|
|
21
21
|
* skip `decode`. Defaults to 0x80 for ASCII compatible encodings.
|
|
22
22
|
*/
|
|
23
|
-
const FastLimit: unique symbol = Symbol.for("effect-parse/Encoding/FastLimit")
|
|
23
|
+
const FastLimit: unique symbol = /*#__PURE__*/ Symbol.for("effect-parse/Encoding/FastLimit")
|
|
24
24
|
|
|
25
25
|
/** @internal */
|
|
26
26
|
export const fastLimit = (enc: Encoding<any>): number =>
|
|
27
27
|
(enc as any)[FastLimit] ?? (enc.asciiCompatible && enc.unit === 1 ? 0x80 : 0)
|
|
28
28
|
|
|
29
|
-
const asciiChars: ReadonlyArray<string> = Array.from({ length: 128 }, (_, i) => String.fromCharCode(i))
|
|
29
|
+
const asciiChars: ReadonlyArray<string> = /*#__PURE__*/ Array.from({ length: 128 }, (_, i) => String.fromCharCode(i))
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The code point that starts `s`: for a string known to hold one, such as
|
|
33
|
+
* one element of `Array.from(string)`. On an empty string it is `NaN`, which,
|
|
34
|
+
* like the `undefined` of `codePointAt(0)`, matches no code point.
|
|
35
|
+
*/
|
|
36
|
+
export const firstCodePoint = (s: string): number => s.codePointAt(0) ?? Number.NaN
|
|
30
37
|
|
|
31
38
|
/** `String.fromCodePoint`, with the ASCII characters cached. */
|
|
32
39
|
export const charOf = (cp: number): string =>
|
|
33
|
-
cp < 128 ? asciiChars[cp]
|
|
40
|
+
cp < 128 ? asciiChars[cp] : cp < 0x10000 ? String.fromCharCode(cp) : String.fromCodePoint(cp)
|
|
34
41
|
|
|
35
42
|
const decodeString = (s: string, i: number, end: number): number => {
|
|
36
43
|
const c = s.charCodeAt(i)
|
|
@@ -52,40 +59,40 @@ export const string: Encoding<string> = {
|
|
|
52
59
|
unit: 1,
|
|
53
60
|
asciiCompatible: true,
|
|
54
61
|
decode: decodeString,
|
|
55
|
-
width: (cp) => cp > 0xffff ? 2 : 1,
|
|
62
|
+
width: (cp) => (cp > 0xffff ? 2 : 1),
|
|
56
63
|
encode: (text) => text,
|
|
57
64
|
text: (s, start, end) => s.slice(start, end),
|
|
58
65
|
[FastLimit]: 0xd800
|
|
59
66
|
} as Encoding<string>
|
|
60
67
|
|
|
61
68
|
const decodeUtf8 = (b: Uint8Array, i: number, end: number): number => {
|
|
62
|
-
const c = b[i]
|
|
69
|
+
const c = b[i]
|
|
63
70
|
if (c < 0x80) return c
|
|
64
71
|
if (c < 0xc2) return Invalid
|
|
65
72
|
if (c < 0xe0) {
|
|
66
73
|
if (i + 1 >= end) return Truncated
|
|
67
|
-
const d = b[i + 1]
|
|
74
|
+
const d = b[i + 1]
|
|
68
75
|
return (d & 0xc0) === 0x80 ? ((c & 0x1f) << 6) | (d & 0x3f) : Invalid
|
|
69
76
|
}
|
|
70
77
|
if (c < 0xf0) {
|
|
71
78
|
if (i + 1 >= end) return Truncated
|
|
72
|
-
const d = b[i + 1]
|
|
79
|
+
const d = b[i + 1]
|
|
73
80
|
// Reject overlong encodings (E0 80..9F) and surrogates (ED A0..BF)
|
|
74
81
|
if (c === 0xe0 ? d < 0xa0 || d > 0xbf : c === 0xed ? d < 0x80 || d > 0x9f : (d & 0xc0) !== 0x80) return Invalid
|
|
75
82
|
if (i + 2 >= end) return Truncated
|
|
76
|
-
const e = b[i + 2]
|
|
83
|
+
const e = b[i + 2]
|
|
77
84
|
return (e & 0xc0) === 0x80 ? ((c & 0x0f) << 12) | ((d & 0x3f) << 6) | (e & 0x3f) : Invalid
|
|
78
85
|
}
|
|
79
86
|
if (c < 0xf5) {
|
|
80
87
|
if (i + 1 >= end) return Truncated
|
|
81
|
-
const d = b[i + 1]
|
|
88
|
+
const d = b[i + 1]
|
|
82
89
|
// Reject overlong encodings (F0 80..8F) and code points above U+10FFFF (F4 90..BF)
|
|
83
90
|
if (c === 0xf0 ? d < 0x90 || d > 0xbf : c === 0xf4 ? d < 0x80 || d > 0x8f : (d & 0xc0) !== 0x80) return Invalid
|
|
84
91
|
if (i + 2 >= end) return Truncated
|
|
85
|
-
const e = b[i + 2]
|
|
92
|
+
const e = b[i + 2]
|
|
86
93
|
if ((e & 0xc0) !== 0x80) return Invalid
|
|
87
94
|
if (i + 3 >= end) return Truncated
|
|
88
|
-
const f = b[i + 3]
|
|
95
|
+
const f = b[i + 3]
|
|
89
96
|
return (f & 0xc0) === 0x80 ? ((c & 0x07) << 18) | ((d & 0x3f) << 12) | ((e & 0x3f) << 6) | (f & 0x3f) : Invalid
|
|
90
97
|
}
|
|
91
98
|
return Invalid
|
|
@@ -95,7 +102,7 @@ const decodeUtf8 = (b: Uint8Array, i: number, end: number): number => {
|
|
|
95
102
|
// `TextDecoder` call, which has a high fixed cost.
|
|
96
103
|
const SHORT = 24
|
|
97
104
|
|
|
98
|
-
const utf8Decoder = new TextDecoder("utf-8", { ignoreBOM: true })
|
|
105
|
+
const utf8Decoder = /*#__PURE__*/ new TextDecoder("utf-8", { ignoreBOM: true })
|
|
99
106
|
|
|
100
107
|
/**
|
|
101
108
|
* Decodes `b[start, end)`. Some platforms' `TextDecoder` rejects views of a
|
|
@@ -105,7 +112,7 @@ const decodeRange = (decoder: TextDecoder, b: Uint8Array, start: number, end: nu
|
|
|
105
112
|
const view = b.subarray(start, end)
|
|
106
113
|
return decoder.decode(b.buffer instanceof ArrayBuffer ? view : view.slice())
|
|
107
114
|
}
|
|
108
|
-
const utf8Encoder = new TextEncoder()
|
|
115
|
+
const utf8Encoder = /*#__PURE__*/ new TextEncoder()
|
|
109
116
|
|
|
110
117
|
/** UTF-8 bytes. */
|
|
111
118
|
export const utf8: Encoding<Uint8Array> = {
|
|
@@ -113,13 +120,13 @@ export const utf8: Encoding<Uint8Array> = {
|
|
|
113
120
|
unit: 1,
|
|
114
121
|
asciiCompatible: true,
|
|
115
122
|
decode: decodeUtf8,
|
|
116
|
-
width: (cp) => cp < 0x80 ? 1 : cp < 0x800 ? 2 : cp < 0x10000 ? 3 : 4,
|
|
123
|
+
width: (cp) => (cp < 0x80 ? 1 : cp < 0x800 ? 2 : cp < 0x10000 ? 3 : 4),
|
|
117
124
|
encode: (text) => utf8Encoder.encode(text),
|
|
118
125
|
text: (b, start, end) => {
|
|
119
126
|
if (end - start > SHORT) return decodeRange(utf8Decoder, b, start, end)
|
|
120
127
|
let out = ""
|
|
121
128
|
for (let i = start; i < end;) {
|
|
122
|
-
const c = b[i]
|
|
129
|
+
const c = b[i]
|
|
123
130
|
if (c < 0x80) {
|
|
124
131
|
out += asciiChars[c]
|
|
125
132
|
i++
|
|
@@ -143,14 +150,14 @@ const makeUtf16 = (littleEndian: boolean): Encoding<Uint8Array> => {
|
|
|
143
150
|
asciiCompatible: false,
|
|
144
151
|
decode: (b, i, end) => {
|
|
145
152
|
if (i + 1 >= end) return Truncated
|
|
146
|
-
const c = b[i + lo]
|
|
153
|
+
const c = b[i + lo] | (b[i + hi] << 8)
|
|
147
154
|
if ((c & 0xf800) !== 0xd800) return c
|
|
148
155
|
if (c > 0xdbff) return Invalid
|
|
149
156
|
if (i + 3 >= end) return Truncated
|
|
150
|
-
const d = b[i + 2 + lo]
|
|
157
|
+
const d = b[i + 2 + lo] | (b[i + 2 + hi] << 8)
|
|
151
158
|
return (d & 0xfc00) === 0xdc00 ? ((c - 0xd800) << 10) + (d - 0xdc00) + 0x10000 : Invalid
|
|
152
159
|
},
|
|
153
|
-
width: (cp) => cp > 0xffff ? 4 : 2,
|
|
160
|
+
width: (cp) => (cp > 0xffff ? 4 : 2),
|
|
154
161
|
encode: (text) => {
|
|
155
162
|
const out = new Uint8Array(text.length * 2)
|
|
156
163
|
for (let i = 0; i < text.length; i++) {
|
|
@@ -164,7 +171,7 @@ const makeUtf16 = (littleEndian: boolean): Encoding<Uint8Array> => {
|
|
|
164
171
|
if (end - start <= SHORT * 2) {
|
|
165
172
|
// The range is well formed, so surrogate pairs can be copied unit by unit
|
|
166
173
|
let out = ""
|
|
167
|
-
for (let i = start; i < end; i += 2) out += String.fromCharCode(b[i + lo]
|
|
174
|
+
for (let i = start; i < end; i += 2) out += String.fromCharCode(b[i + lo] | (b[i + hi] << 8))
|
|
168
175
|
return out
|
|
169
176
|
}
|
|
170
177
|
decoder ??= new TextDecoder(littleEndian ? "utf-16le" : "utf-16be", { ignoreBOM: true })
|
|
@@ -174,17 +181,17 @@ const makeUtf16 = (littleEndian: boolean): Encoding<Uint8Array> => {
|
|
|
174
181
|
}
|
|
175
182
|
|
|
176
183
|
/** UTF-16 bytes, little endian. */
|
|
177
|
-
export const utf16le: Encoding<Uint8Array> = makeUtf16(true)
|
|
184
|
+
export const utf16le: Encoding<Uint8Array> = /*#__PURE__*/ makeUtf16(true)
|
|
178
185
|
|
|
179
186
|
/** UTF-16 bytes, big endian. */
|
|
180
|
-
export const utf16be: Encoding<Uint8Array> = makeUtf16(false)
|
|
187
|
+
export const utf16be: Encoding<Uint8Array> = /*#__PURE__*/ makeUtf16(false)
|
|
181
188
|
|
|
182
189
|
/** ISO-8859-1: every byte is the code point of the same value. */
|
|
183
190
|
export const latin1: Encoding<Uint8Array> = {
|
|
184
191
|
name: "ISO-8859-1",
|
|
185
192
|
unit: 1,
|
|
186
193
|
asciiCompatible: true,
|
|
187
|
-
decode: (b, i) => b[i]
|
|
194
|
+
decode: (b, i) => b[i],
|
|
188
195
|
width: () => 1,
|
|
189
196
|
encode: (text) => {
|
|
190
197
|
const out = new Uint8Array(text.length)
|
|
@@ -303,7 +310,7 @@ export const scan = (
|
|
|
303
310
|
stop = End
|
|
304
311
|
break
|
|
305
312
|
}
|
|
306
|
-
const c = b[i]
|
|
313
|
+
const c = b[i]
|
|
307
314
|
if (c < fast) {
|
|
308
315
|
if (test(c) === negate) break
|
|
309
316
|
i++
|
|
@@ -345,10 +352,10 @@ export const validate = (enc: Encoding<any>, state: State<any>, start: number, e
|
|
|
345
352
|
// Case folding
|
|
346
353
|
// -----------------------------------------------------------------------------
|
|
347
354
|
|
|
348
|
-
const foldCache = new Map<number, number>()
|
|
355
|
+
const foldCache = /*#__PURE__*/ new Map<number, number>()
|
|
349
356
|
|
|
350
357
|
const single = (s: string): number => {
|
|
351
|
-
const cp = s
|
|
358
|
+
const cp = firstCodePoint(s)
|
|
352
359
|
return s.length === (cp > 0xffff ? 2 : 1) ? cp : -1
|
|
353
360
|
}
|
|
354
361
|
|