@thomasfosterau/effect-parse 0.2.0 → 0.4.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 +44 -1
- package/dist/Ascii.d.ts +0 -2
- package/dist/Ascii.d.ts.map +1 -1
- package/dist/Ascii.js +6 -6
- package/dist/Ascii.js.map +1 -1
- package/dist/Binary.d.ts +0 -2
- package/dist/Binary.d.ts.map +1 -1
- package/dist/Binary.js +1 -2
- package/dist/Bits.d.ts +0 -2
- package/dist/Bits.d.ts.map +1 -1
- package/dist/Bits.js +2 -3
- package/dist/Bits.js.map +1 -1
- package/dist/Compiler.d.ts +0 -2
- package/dist/Compiler.d.ts.map +1 -1
- package/dist/Compiler.js +1 -2
- package/dist/Encoding.d.ts +0 -2
- package/dist/Encoding.d.ts.map +1 -1
- package/dist/Encoding.js +1 -2
- package/dist/Expression.d.ts +0 -2
- package/dist/Expression.d.ts.map +1 -1
- package/dist/Expression.js +61 -18
- package/dist/Expression.js.map +1 -1
- package/dist/Input.d.ts +80 -7
- package/dist/Input.d.ts.map +1 -1
- package/dist/Input.js +149 -18
- package/dist/Input.js.map +1 -1
- package/dist/Nom.d.ts +1 -3
- package/dist/Nom.d.ts.map +1 -1
- package/dist/Nom.js +1 -2
- package/dist/ParseError.d.ts +0 -2
- package/dist/ParseError.d.ts.map +1 -1
- package/dist/ParseError.js +6 -10
- package/dist/ParseError.js.map +1 -1
- package/dist/Parser.d.ts +2 -2
- package/dist/Parser.d.ts.map +1 -1
- package/dist/Parser.js +58 -32
- package/dist/Parser.js.map +1 -1
- package/dist/ParserSchema.d.ts +198 -2
- package/dist/ParserSchema.d.ts.map +1 -1
- package/dist/ParserSchema.js +646 -18
- package/dist/ParserSchema.js.map +1 -1
- package/dist/ParserStream.d.ts +0 -2
- package/dist/ParserStream.d.ts.map +1 -1
- package/dist/ParserStream.js +15 -9
- package/dist/ParserStream.js.map +1 -1
- package/dist/Syntax.d.ts +0 -2
- package/dist/Syntax.d.ts.map +1 -1
- package/dist/Syntax.js +13 -11
- package/dist/Syntax.js.map +1 -1
- package/dist/Text.d.ts +0 -2
- package/dist/Text.d.ts.map +1 -1
- package/dist/Text.js +1 -2
- package/dist/Token.d.ts +0 -2
- package/dist/Token.d.ts.map +1 -1
- package/dist/Token.js +1 -2
- package/dist/internal/compiler/closure.d.ts +5 -2
- package/dist/internal/compiler/closure.d.ts.map +1 -1
- package/dist/internal/compiler/closure.js +160 -92
- package/dist/internal/compiler/closure.js.map +1 -1
- package/dist/internal/compiler/entries.d.ts +0 -2
- package/dist/internal/compiler/entries.d.ts.map +1 -1
- package/dist/internal/compiler/entries.js +6 -7
- package/dist/internal/compiler/entries.js.map +1 -1
- package/dist/internal/compiler/first.d.ts +0 -2
- package/dist/internal/compiler/first.d.ts.map +1 -1
- package/dist/internal/compiler/first.js +10 -10
- package/dist/internal/compiler/first.js.map +1 -1
- package/dist/internal/compiler/jit.d.ts +0 -2
- package/dist/internal/compiler/jit.d.ts.map +1 -1
- package/dist/internal/compiler/jit.js +52 -51
- package/dist/internal/compiler/jit.js.map +1 -1
- package/dist/internal/compiler/registry.d.ts +0 -2
- package/dist/internal/compiler/registry.d.ts.map +1 -1
- package/dist/internal/compiler/registry.js +1 -2
- package/dist/internal/compiler/sets.d.ts +2 -2
- package/dist/internal/compiler/sets.d.ts.map +1 -1
- package/dist/internal/compiler/sets.js +8 -3
- package/dist/internal/compiler/sets.js.map +1 -1
- package/dist/internal/entries.d.ts +0 -2
- package/dist/internal/entries.d.ts.map +1 -1
- package/dist/internal/entries.js +1 -2
- package/dist/internal/key.d.ts +10 -0
- package/dist/internal/key.d.ts.map +1 -0
- package/dist/internal/key.js +11 -0
- package/dist/internal/key.js.map +1 -0
- package/dist/internal/node.d.ts +11 -4
- package/dist/internal/node.d.ts.map +1 -1
- package/dist/internal/node.js +15 -3
- package/dist/internal/node.js.map +1 -1
- package/dist/internal/numbers.d.ts +0 -2
- package/dist/internal/numbers.d.ts.map +1 -1
- package/dist/internal/numbers.js +1 -2
- package/dist/internal/resume.d.ts +5 -6
- package/dist/internal/resume.d.ts.map +1 -1
- package/dist/internal/resume.js +1 -2
- package/dist/internal/resume.js.map +1 -1
- package/dist/internal/run.d.ts +0 -2
- package/dist/internal/run.d.ts.map +1 -1
- package/dist/internal/run.js +2 -3
- package/dist/internal/run.js.map +1 -1
- package/dist/internal/stack.d.ts +0 -2
- package/dist/internal/stack.d.ts.map +1 -1
- package/dist/internal/stack.js +12 -3
- package/dist/internal/stack.js.map +1 -1
- package/dist/internal/syntax.d.ts +30 -19
- package/dist/internal/syntax.d.ts.map +1 -1
- package/dist/internal/syntax.js +74 -63
- package/dist/internal/syntax.js.map +1 -1
- package/dist/internal/text.d.ts +5 -2
- package/dist/internal/text.d.ts.map +1 -1
- package/dist/internal/text.js +14 -5
- package/dist/internal/text.js.map +1 -1
- package/dist/internal/ties.d.ts +28 -0
- package/dist/internal/ties.d.ts.map +1 -0
- package/dist/internal/ties.js +66 -0
- package/dist/internal/ties.js.map +1 -0
- package/dist/internal/tokens.d.ts +10 -9
- package/dist/internal/tokens.d.ts.map +1 -1
- package/dist/internal/tokens.js +4 -8
- package/dist/internal/tokens.js.map +1 -1
- package/package.json +6 -6
- package/src/Ascii.ts +6 -5
- package/src/Bits.ts +1 -1
- package/src/Expression.ts +27 -23
- package/src/Input.ts +157 -21
- package/src/ParseError.ts +10 -8
- package/src/Parser.ts +67 -32
- package/src/ParserSchema.ts +982 -23
- package/src/ParserStream.ts +20 -8
- package/src/Syntax.ts +12 -9
- package/src/internal/compiler/closure.ts +202 -90
- package/src/internal/compiler/entries.ts +14 -11
- package/src/internal/compiler/first.ts +9 -8
- package/src/internal/compiler/jit.ts +95 -74
- package/src/internal/compiler/sets.ts +7 -0
- package/src/internal/key.ts +11 -0
- package/src/internal/node.ts +19 -2
- package/src/internal/resume.ts +5 -4
- package/src/internal/run.ts +1 -1
- package/src/internal/stack.ts +6 -1
- package/src/internal/syntax.ts +80 -84
- package/src/internal/text.ts +24 -4
- package/src/internal/ties.ts +83 -0
- package/src/internal/tokens.ts +20 -13
package/src/internal/syntax.ts
CHANGED
|
@@ -14,23 +14,32 @@
|
|
|
14
14
|
* completes and adopts them. Nodes built by an attempt that is later
|
|
15
15
|
* abandoned must not end up in the tree. Every combinator that abandons an
|
|
16
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
|
|
18
|
-
* are exactly those that end after the position the
|
|
19
|
-
*
|
|
20
|
-
* lookahead (`peek`, `not`) and custom parsers written with
|
|
17
|
+
* with `State.rewind`, and every node has a positive width, so the nodes of
|
|
18
|
+
* an abandoned attempt are exactly those that end after the position the
|
|
19
|
+
* cursor moves back to: {@link SyntaxBuilder} drops them when told of the rewind. This also
|
|
20
|
+
* covers lookahead (`peek`, `not`) and custom parsers written with
|
|
21
|
+
* `Parser.make`, which rewind the same way.
|
|
22
|
+
*
|
|
23
|
+
* `pos` stays a plain field, not an accessor that would watch every move: a
|
|
24
|
+
* `pos` accessor on this subclass made every parser's cursor accesses
|
|
25
|
+
* polymorphic, slowing ordinary parses by about a fifth in any program that
|
|
26
|
+
* also built a syntax tree, and the syntax parse itself about twofold.
|
|
21
27
|
*
|
|
22
28
|
* For incremental parsing, every node records how far its parse looked: the
|
|
23
29
|
* furthest position the cursor reached and the furthest failure recorded
|
|
24
|
-
* while it was being parsed (its *lookahead*).
|
|
30
|
+
* while it was being parsed (its *lookahead*). The cursor only moves forward
|
|
31
|
+
* between rewinds, so the furthest position is where it was at a rewind, or
|
|
32
|
+
* where it is now. A node from the previous tree
|
|
25
33
|
* is reused at a position when it was built by the same `Syntax.node`, and
|
|
26
34
|
* the input it looked at, plus a margin for the tokens that read a little
|
|
27
35
|
* past where they stop, is unchanged.
|
|
28
36
|
*/
|
|
37
|
+
import * as Arr from "effect/Array"
|
|
29
38
|
import * as Predicate from "effect/Predicate"
|
|
30
|
-
import type * as Context from "effect/Context"
|
|
31
39
|
import type { Encoding } from "../Encoding.js"
|
|
32
|
-
import type { ErrMode, Input
|
|
40
|
+
import type { ErrMode, Input } from "../Input.js"
|
|
33
41
|
import { Fail, Failure, State } from "../Input.js"
|
|
42
|
+
import type { Key } from "./key.js"
|
|
34
43
|
|
|
35
44
|
/** What a failure inside an `ERROR` node was. Offsets are relative to the node. */
|
|
36
45
|
export interface ErrorInfo {
|
|
@@ -44,7 +53,7 @@ export class Green {
|
|
|
44
53
|
readonly kind: string,
|
|
45
54
|
readonly named: boolean,
|
|
46
55
|
/** The `Syntax.node` that built it; `undefined` for roots and errors, which are never reused alone. */
|
|
47
|
-
readonly key:
|
|
56
|
+
readonly key: Key | undefined,
|
|
48
57
|
readonly width: number,
|
|
49
58
|
/** How far its parse looked, relative to its start (at least `width`). */
|
|
50
59
|
readonly lookahead: number,
|
|
@@ -103,7 +112,7 @@ export const absolute = (f: Failure, start: number): Failure => rebase(f, -start
|
|
|
103
112
|
const build = (
|
|
104
113
|
kind: string,
|
|
105
114
|
named: boolean,
|
|
106
|
-
key:
|
|
115
|
+
key: Key | undefined,
|
|
107
116
|
start: number,
|
|
108
117
|
end: number,
|
|
109
118
|
lookahead: number,
|
|
@@ -153,7 +162,7 @@ export interface Previous {
|
|
|
153
162
|
|
|
154
163
|
/** The hooks that `Syntax` combinators and `Parser.resumeAfter`/`retryAfter` call. */
|
|
155
164
|
export interface Builder {
|
|
156
|
-
node(state: State<any>, step: (state: State<any>) => unknown, key:
|
|
165
|
+
node(state: State<any>, step: (state: State<any>) => unknown, key: Key, kind: string, named: boolean): unknown
|
|
157
166
|
field(state: State<any>, step: (state: State<any>) => unknown, name: string): unknown
|
|
158
167
|
/** After error recovery: `[start, pos)` was skipped after `failure`. */
|
|
159
168
|
skipped(start: number, failure: Failure, mode: ErrMode): void
|
|
@@ -162,74 +171,61 @@ export interface Builder {
|
|
|
162
171
|
* where they leave the cursor or record a failure.
|
|
163
172
|
*/
|
|
164
173
|
read(end: number): void
|
|
174
|
+
/** A failure or hint was recorded at `offset`. */
|
|
175
|
+
reach(offset: number): void
|
|
176
|
+
/** The cursor moved back from `from` to `to` (see `State.rewind`). */
|
|
177
|
+
rewound(from: number, to: number): void
|
|
165
178
|
}
|
|
166
179
|
|
|
167
|
-
/**
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
180
|
+
/**
|
|
181
|
+
* The tree builder of a `Syntax` parse, reached through `state.syntax`. It
|
|
182
|
+
* is not a `State` subclass: one class of state keeps every parser's cursor
|
|
183
|
+
* and failure accesses monomorphic, whether or not the program also builds
|
|
184
|
+
* syntax trees. `State` calls {@link reach} and {@link rewound} itself.
|
|
185
|
+
*/
|
|
186
|
+
export class SyntaxBuilder implements Builder {
|
|
187
|
+
/**
|
|
188
|
+
* The furthest position the cursor reached before a rewind, or failure
|
|
189
|
+
* recorded, since the innermost open node started; {@link looked} adds
|
|
190
|
+
* where the cursor is now.
|
|
191
|
+
*/
|
|
192
|
+
highWater: number
|
|
174
193
|
/** Completed nodes waiting for their parent, in input order. */
|
|
175
|
-
|
|
176
|
-
declare previous: Previous | undefined
|
|
194
|
+
readonly stack: Array<Child> = []
|
|
177
195
|
/** How many nodes were reused from the previous tree. */
|
|
178
|
-
|
|
196
|
+
reused = 0
|
|
179
197
|
/** Where {@link find} found the last node it found. */
|
|
180
|
-
|
|
181
|
-
|
|
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
|
-
}
|
|
198
|
+
lastParent: Green | undefined = undefined
|
|
199
|
+
lastParentStart = 0
|
|
201
200
|
|
|
202
201
|
constructor(
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
readonly context?: Context.Context<never> | undefined
|
|
206
|
-
},
|
|
207
|
-
previous: Previous | undefined
|
|
202
|
+
readonly state: State<any>,
|
|
203
|
+
readonly previous: Previous | undefined
|
|
208
204
|
) {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
this.stack = []
|
|
212
|
-
this.previous = previous
|
|
213
|
-
this.reused = 0
|
|
214
|
-
this.lastParent = undefined
|
|
215
|
-
this.lastParentStart = 0
|
|
216
|
-
this.syntax = this
|
|
205
|
+
this.highWater = state.pos
|
|
206
|
+
state.syntax = this
|
|
217
207
|
// Error recovery is always on: like Tree-sitter, a syntax parse always produces a tree
|
|
218
|
-
|
|
208
|
+
state.recovered = []
|
|
219
209
|
}
|
|
220
210
|
|
|
221
|
-
|
|
222
|
-
if (
|
|
223
|
-
return super.failWith(mode, failure)
|
|
211
|
+
reach(offset: number): void {
|
|
212
|
+
if (offset > this.highWater) this.highWater = offset
|
|
224
213
|
}
|
|
225
214
|
|
|
226
|
-
|
|
227
|
-
if (
|
|
228
|
-
|
|
215
|
+
rewound(from: number, to: number): void {
|
|
216
|
+
if (from > this.highWater) this.highWater = from
|
|
217
|
+
// An attempt was abandoned (or a lookahead finished): drop its nodes
|
|
218
|
+
const stack = this.stack
|
|
219
|
+
while (Arr.isArrayNonEmpty(stack) && Arr.lastNonEmpty(stack).end > to) stack.pop()
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** How far the parse looked: {@link highWater}, or the cursor if it is further. */
|
|
223
|
+
private looked(): number {
|
|
224
|
+
return this.state.pos > this.highWater ? this.state.pos : this.highWater
|
|
229
225
|
}
|
|
230
226
|
|
|
231
|
-
node(state: State<any>, step: (state: State<any>) => unknown, key:
|
|
232
|
-
const start = this.
|
|
227
|
+
node(state: State<any>, step: (state: State<any>) => unknown, key: Key, kind: string, named: boolean): unknown {
|
|
228
|
+
const start = this.state.pos
|
|
233
229
|
const stack = this.stack
|
|
234
230
|
if (this.previous !== undefined) {
|
|
235
231
|
const green = this.reusable(this.previous, key, start)
|
|
@@ -242,7 +238,7 @@ export class SyntaxState<I extends Input = Input> extends State<I> implements Bu
|
|
|
242
238
|
const d = green.diagnostics
|
|
243
239
|
if (d !== undefined) {
|
|
244
240
|
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)
|
|
241
|
+
if (d.hintExpected !== undefined) this.state.setHint(start + d.hintOffset, d.hintExpected)
|
|
246
242
|
}
|
|
247
243
|
stack.push(new Child(green, start, end, undefined))
|
|
248
244
|
return green.value
|
|
@@ -250,34 +246,34 @@ export class SyntaxState<I extends Input = Input> extends State<I> implements Bu
|
|
|
250
246
|
}
|
|
251
247
|
const mark = stack.length
|
|
252
248
|
const outer = this.highWater
|
|
253
|
-
const outerDeepest = this.deepest
|
|
254
|
-
const hintOffset = this.hintOffset
|
|
255
|
-
const hintExpected = this.hintExpected
|
|
249
|
+
const outerDeepest = this.state.deepest
|
|
250
|
+
const hintOffset = this.state.hintOffset
|
|
251
|
+
const hintExpected = this.state.hintExpected
|
|
256
252
|
this.highWater = start
|
|
257
253
|
// Collect the furthest failure of this node alone, then merge it as `failWith` would have
|
|
258
|
-
this.deepest = undefined
|
|
254
|
+
this.state.deepest = undefined
|
|
259
255
|
const a = step(state)
|
|
260
|
-
const looked = this.
|
|
261
|
-
|
|
262
|
-
const deepest = this.deepest as Failure | undefined
|
|
263
|
-
this.deepest = outerDeepest
|
|
256
|
+
const looked = this.looked()
|
|
257
|
+
this.highWater = outer > looked ? outer : looked
|
|
258
|
+
const deepest = this.state.deepest as Failure | undefined
|
|
259
|
+
this.state.deepest = outerDeepest
|
|
264
260
|
if (deepest !== undefined) this.deepen(deepest)
|
|
265
261
|
if (a === Fail) {
|
|
266
262
|
if (stack.length > mark) stack.length = mark
|
|
267
263
|
return a
|
|
268
264
|
}
|
|
269
|
-
const end = this.
|
|
265
|
+
const end = this.state.pos
|
|
270
266
|
// Like Tree-sitter's rules, nodes are never empty: an empty match adds nothing to the tree
|
|
271
267
|
if (end === start) return a
|
|
272
|
-
const hinted = this.hintOffset !== hintOffset || this.hintExpected !== hintExpected
|
|
268
|
+
const hinted = this.state.hintOffset !== hintOffset || this.state.hintExpected !== hintExpected
|
|
273
269
|
const diagnostics: Diagnostics | undefined =
|
|
274
270
|
deepest === undefined && !hinted
|
|
275
271
|
? undefined
|
|
276
272
|
: {
|
|
277
273
|
deepest,
|
|
278
274
|
base: start,
|
|
279
|
-
hintOffset: this.hintOffset - start,
|
|
280
|
-
hintExpected: hinted ? this.hintExpected : undefined
|
|
275
|
+
hintOffset: this.state.hintOffset - start,
|
|
276
|
+
hintExpected: hinted ? this.state.hintExpected : undefined
|
|
281
277
|
}
|
|
282
278
|
const entries = stack.splice(mark)
|
|
283
279
|
stack.push(
|
|
@@ -293,7 +289,7 @@ export class SyntaxState<I extends Input = Input> extends State<I> implements Bu
|
|
|
293
289
|
|
|
294
290
|
/** Records `failure` as the furthest failure if it is, as `failWith` does. */
|
|
295
291
|
private deepen(failure: Failure): void {
|
|
296
|
-
if (this.deepest === undefined || failure.offset >= this.deepest.offset) this.deepest = failure
|
|
292
|
+
if (this.state.deepest === undefined || failure.offset >= this.state.deepest.offset) this.state.deepest = failure
|
|
297
293
|
}
|
|
298
294
|
|
|
299
295
|
field(state: State<any>, step: (state: State<any>) => unknown, name: string): unknown {
|
|
@@ -308,8 +304,8 @@ export class SyntaxState<I extends Input = Input> extends State<I> implements Bu
|
|
|
308
304
|
const stack = this.stack
|
|
309
305
|
let i = stack.length
|
|
310
306
|
while (i > 0 && stack[i - 1].start >= start && stack[i - 1].end > start) i--
|
|
311
|
-
const end = this.
|
|
312
|
-
const green = build(errorKind, true, undefined, start, end, this.
|
|
307
|
+
const end = this.state.pos
|
|
308
|
+
const green = build(errorKind, true, undefined, start, end, this.looked(), stack.splice(i), undefined, {
|
|
313
309
|
failure: rebase(failure, start),
|
|
314
310
|
mode
|
|
315
311
|
})
|
|
@@ -323,9 +319,9 @@ export class SyntaxState<I extends Input = Input> extends State<I> implements Bu
|
|
|
323
319
|
*/
|
|
324
320
|
finish(kind: string, value: unknown, fatal: ErrorInfo | undefined, errorStart: number): Green {
|
|
325
321
|
const entries = this.stack.splice(0)
|
|
326
|
-
const end = this.end
|
|
322
|
+
const end = this.state.end
|
|
327
323
|
if (fatal !== undefined) {
|
|
328
|
-
const last = entries
|
|
324
|
+
const last = Arr.isArrayNonEmpty(entries) ? Arr.lastNonEmpty(entries).end : 0
|
|
329
325
|
const start = Math.max(Math.min(errorStart, end), last)
|
|
330
326
|
const error = build(errorKind, true, undefined, start, end, end, [], undefined, {
|
|
331
327
|
failure: rebase(fatal.failure, start),
|
|
@@ -333,7 +329,7 @@ export class SyntaxState<I extends Input = Input> extends State<I> implements Bu
|
|
|
333
329
|
})
|
|
334
330
|
entries.push(new Child(error, start, end, undefined))
|
|
335
331
|
}
|
|
336
|
-
return build(kind, true, undefined, 0, end, Math.max(end, this.
|
|
332
|
+
return build(kind, true, undefined, 0, end, Math.max(end, this.looked()), entries, value, undefined)
|
|
337
333
|
}
|
|
338
334
|
|
|
339
335
|
/**
|
|
@@ -342,7 +338,7 @@ export class SyntaxState<I extends Input = Input> extends State<I> implements Bu
|
|
|
342
338
|
* so the search starts from the parent of the last one found when
|
|
343
339
|
* `offset` lies inside it: no ancestor of that parent can start there.
|
|
344
340
|
*/
|
|
345
|
-
private find(root: Green, offset: number, key:
|
|
341
|
+
private find(root: Green, offset: number, key: Key): Green | undefined {
|
|
346
342
|
let node = root
|
|
347
343
|
let start = 0
|
|
348
344
|
const last = this.lastParent
|
|
@@ -380,7 +376,7 @@ export class SyntaxState<I extends Input = Input> extends State<I> implements Bu
|
|
|
380
376
|
}
|
|
381
377
|
|
|
382
378
|
/** 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:
|
|
379
|
+
private reusable(previous: Previous, key: Key, pos: number): Green | undefined {
|
|
384
380
|
const segments = previous.segments
|
|
385
381
|
// The segment containing `pos`
|
|
386
382
|
let lo = 0
|
package/src/internal/text.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import * as Arr from "effect/Array"
|
|
2
|
+
import * as Predicate from "effect/Predicate"
|
|
3
|
+
import * as Str from "effect/String"
|
|
1
4
|
import type { Encoding } from "../Encoding.js"
|
|
2
5
|
import type { Fail, State } from "../Input.js"
|
|
3
6
|
|
|
@@ -22,6 +25,17 @@ export const End = -3
|
|
|
22
25
|
*/
|
|
23
26
|
const FastLimit: unique symbol = /*#__PURE__*/ Symbol.for("effect-parse/Encoding/FastLimit")
|
|
24
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Whether an input, a literal or a chunk has no units (characters, bytes or
|
|
30
|
+
* tokens).
|
|
31
|
+
*/
|
|
32
|
+
export const isEmptyUnits = (units: string | Uint8Array | ReadonlyArray<unknown>): boolean =>
|
|
33
|
+
Predicate.isString(units)
|
|
34
|
+
? Str.isEmpty(units)
|
|
35
|
+
: units instanceof Uint8Array
|
|
36
|
+
? units.byteLength === 0
|
|
37
|
+
: Arr.isReadonlyArrayEmpty(units)
|
|
38
|
+
|
|
25
39
|
/** @internal */
|
|
26
40
|
export const fastLimit = (enc: Encoding<any>): number =>
|
|
27
41
|
(enc as any)[FastLimit] ?? (enc.asciiCompatible && enc.unit === 1 ? 0x80 : 0)
|
|
@@ -115,6 +129,7 @@ const decodeRange = (decoder: TextDecoder, b: Uint8Array, start: number, end: nu
|
|
|
115
129
|
const utf8Encoder = /*#__PURE__*/ new TextEncoder()
|
|
116
130
|
|
|
117
131
|
/** UTF-8 bytes. */
|
|
132
|
+
// SAFETY: an `Encoding` with the internal `FastLimit` key, which its type doesn't declare
|
|
118
133
|
export const utf8: Encoding<Uint8Array> = {
|
|
119
134
|
name: "UTF-8",
|
|
120
135
|
unit: 1,
|
|
@@ -137,13 +152,17 @@ export const utf8: Encoding<Uint8Array> = {
|
|
|
137
152
|
}
|
|
138
153
|
}
|
|
139
154
|
return out
|
|
140
|
-
}
|
|
141
|
-
|
|
155
|
+
},
|
|
156
|
+
// Every built-in encoding has the same keys in the same order, so code
|
|
157
|
+
// reading them sees one object shape
|
|
158
|
+
[FastLimit]: 0x80
|
|
159
|
+
} as Encoding<Uint8Array>
|
|
142
160
|
|
|
143
161
|
const makeUtf16 = (littleEndian: boolean): Encoding<Uint8Array> => {
|
|
144
162
|
const lo = littleEndian ? 0 : 1
|
|
145
163
|
const hi = 1 - lo
|
|
146
164
|
let decoder: TextDecoder | undefined
|
|
165
|
+
// SAFETY: an `Encoding` with the internal `FastLimit` key, which its type doesn't declare
|
|
147
166
|
return {
|
|
148
167
|
name: littleEndian ? "UTF-16LE" : "UTF-16BE",
|
|
149
168
|
unit: 2,
|
|
@@ -176,8 +195,9 @@ const makeUtf16 = (littleEndian: boolean): Encoding<Uint8Array> => {
|
|
|
176
195
|
}
|
|
177
196
|
decoder ??= new TextDecoder(littleEndian ? "utf-16le" : "utf-16be", { ignoreBOM: true })
|
|
178
197
|
return decodeRange(decoder, b, start, end)
|
|
179
|
-
}
|
|
180
|
-
|
|
198
|
+
},
|
|
199
|
+
[FastLimit]: 0
|
|
200
|
+
} as Encoding<Uint8Array>
|
|
181
201
|
}
|
|
182
202
|
|
|
183
203
|
/** UTF-16 bytes, little endian. */
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tied failures, merged only when read.
|
|
3
|
+
*
|
|
4
|
+
* A combinator that tries several parsers at the same position (an
|
|
5
|
+
* alternation, a permutation round, an operator table) reports the merge
|
|
6
|
+
* of their failures, left to right, as `mergeFailures` would. Most of the
|
|
7
|
+
* time one of the later attempts succeeds and the merge is thrown away, so
|
|
8
|
+
* {@link TiedFailures} keeps the last two failures tied with the running
|
|
9
|
+
* one unmerged, and {@link TiedFailures.fail} hands them to the state with
|
|
10
|
+
* `State.failTies`, which merges them on first read (#38, #44).
|
|
11
|
+
*
|
|
12
|
+
* `Parser.alt` does the same fold in locals, since it is the hottest
|
|
13
|
+
* combinator and cannot afford an accumulator per call.
|
|
14
|
+
*/
|
|
15
|
+
import type { Failure, Fail, Input, State } from "../Input.js"
|
|
16
|
+
import { mergeExpected, mergeTiedFailures } from "../Input.js"
|
|
17
|
+
|
|
18
|
+
/** The failures of a round of attempts, folded lazily. */
|
|
19
|
+
export class TiedFailures {
|
|
20
|
+
/** The running failure, before the pending ties are merged into it. */
|
|
21
|
+
failure: Failure | undefined = undefined
|
|
22
|
+
/** The oldest pending tie, at `failure`'s offset. */
|
|
23
|
+
tie1: Failure | undefined = undefined
|
|
24
|
+
/** The newest pending tie, at `failure`'s offset. */
|
|
25
|
+
tie2: Failure | undefined = undefined
|
|
26
|
+
|
|
27
|
+
/** Forgets every failure, for a new round. */
|
|
28
|
+
clear(): void {
|
|
29
|
+
this.failure = undefined
|
|
30
|
+
this.tie1 = undefined
|
|
31
|
+
this.tie2 = undefined
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Folds in the failure the latest attempt left on `state`, taking over
|
|
36
|
+
* its own unmerged ties when it is the furthest.
|
|
37
|
+
*/
|
|
38
|
+
add<I extends Input>(state: State<I>): void {
|
|
39
|
+
const head = state.failureHead
|
|
40
|
+
const running = this.failure
|
|
41
|
+
if (running === undefined || head.offset > running.offset) {
|
|
42
|
+
this.failure = head
|
|
43
|
+
this.tie1 = state.failureTie1
|
|
44
|
+
this.tie2 = state.failureTie2
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
if (head.offset < running.offset) return
|
|
48
|
+
const failure = state.failure
|
|
49
|
+
const tie1 = this.tie1
|
|
50
|
+
if (tie1 === undefined) this.tie1 = failure
|
|
51
|
+
else if (this.tie2 === undefined) this.tie2 = failure
|
|
52
|
+
else {
|
|
53
|
+
// A third tie: fold the oldest pending one in, keeping the order
|
|
54
|
+
this.failure = mergeTiedFailures(running, tie1, mergeExpected(running.expected, tie1.expected))
|
|
55
|
+
this.tie1 = this.tie2
|
|
56
|
+
this.tie2 = failure
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Fails `state` with the folded failure (recoverably), or with its
|
|
62
|
+
* current failure when nothing was folded.
|
|
63
|
+
*/
|
|
64
|
+
fail<I extends Input>(state: State<I>): Fail {
|
|
65
|
+
const failure = this.failure
|
|
66
|
+
if (failure === undefined) return state.failWith("Backtrack", state.failure)
|
|
67
|
+
const tie1 = this.tie1
|
|
68
|
+
return tie1 === undefined ? state.failWith("Backtrack", failure) : state.failTies(failure, tie1, this.tie2)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Records the folded expectations as a hint when the folded failure lies
|
|
73
|
+
* where `state` stopped (an attempt that fails without consuming input).
|
|
74
|
+
*/
|
|
75
|
+
hint<I extends Input>(state: State<I>): void {
|
|
76
|
+
const failure = this.failure
|
|
77
|
+
if (failure === undefined || failure.offset !== state.pos) return
|
|
78
|
+
let expected = failure.expected
|
|
79
|
+
if (this.tie1 !== undefined) expected = mergeExpected(expected, this.tie1.expected)
|
|
80
|
+
if (this.tie2 !== undefined) expected = mergeExpected(expected, this.tie2.expected)
|
|
81
|
+
state.setHint(failure.offset, expected)
|
|
82
|
+
}
|
|
83
|
+
}
|
package/src/internal/tokens.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// fallow-ignore-file duplicate-export -- `End` and `scan` here are the token-set versions of the text
|
|
2
2
|
// helpers in ./text.ts; both are internal modules, imported by path, never re-exported from one barrel.
|
|
3
3
|
import * as Predicate from "effect/Predicate"
|
|
4
|
+
import * as Result from "effect/Result"
|
|
4
5
|
import type { Input, State } from "../Input.js"
|
|
5
6
|
import { charOf, firstCodePoint } from "./text.js"
|
|
6
7
|
|
|
@@ -160,11 +161,10 @@ const compileCodeSafe = (set: unknown, kind: "string" | "code"): ((code: number)
|
|
|
160
161
|
return false
|
|
161
162
|
}
|
|
162
163
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
}
|
|
164
|
+
return Result.getOrElse(
|
|
165
|
+
Result.try(() => compileCode(set)),
|
|
166
|
+
(): ((code: number) => boolean) => () => false
|
|
167
|
+
)
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
/**
|
|
@@ -276,17 +276,18 @@ export const nextIf = (state: State<Input>, m: AnyMatcher, negate: boolean): unk
|
|
|
276
276
|
}
|
|
277
277
|
}
|
|
278
278
|
|
|
279
|
+
/** How far a {@link scan} got. */
|
|
280
|
+
export interface Scanned extends Advanced {
|
|
281
|
+
/** Whether the end of input was hit before `max` tokens. */
|
|
282
|
+
readonly hitEnd: boolean
|
|
283
|
+
}
|
|
284
|
+
|
|
279
285
|
/**
|
|
280
286
|
* Scans forward while tokens satisfy the matcher (xor `negate`), up to `max`
|
|
281
287
|
* tokens. Returns the offset reached, the number of tokens matched, and
|
|
282
288
|
* whether the end of input was hit before `max` tokens.
|
|
283
289
|
*/
|
|
284
|
-
export const scan = (
|
|
285
|
-
state: State<Input>,
|
|
286
|
-
m: AnyMatcher,
|
|
287
|
-
negate: boolean,
|
|
288
|
-
max: number
|
|
289
|
-
): { readonly offset: number; readonly count: number; readonly hitEnd: boolean } => {
|
|
290
|
+
export const scan = (state: State<Input>, m: AnyMatcher, negate: boolean, max: number): Scanned => {
|
|
290
291
|
let i = state.pos
|
|
291
292
|
const end = state.end
|
|
292
293
|
let count = 0
|
|
@@ -335,8 +336,14 @@ export const scan = (
|
|
|
335
336
|
}
|
|
336
337
|
}
|
|
337
338
|
|
|
339
|
+
/** The offset reached and the number of tokens passed. */
|
|
340
|
+
export interface Advanced {
|
|
341
|
+
readonly offset: number
|
|
342
|
+
readonly count: number
|
|
343
|
+
}
|
|
344
|
+
|
|
338
345
|
/** Advances `n` tokens from `start`, returning the offset reached and the count. */
|
|
339
|
-
export const advance = (state: State<Input>, start: number, n: number):
|
|
346
|
+
export const advance = (state: State<Input>, start: number, n: number): Advanced => {
|
|
340
347
|
if (state.inputKind !== 0) {
|
|
341
348
|
const offset = Math.min(state.end, start + n)
|
|
342
349
|
return { offset, count: offset - start }
|
|
@@ -379,7 +386,7 @@ export const indexOf = (state: State<Input>, needle: Input, from: number): numbe
|
|
|
379
386
|
case 1: {
|
|
380
387
|
const hay = state.input as Uint8Array
|
|
381
388
|
const n = needle as Uint8Array
|
|
382
|
-
if (n.
|
|
389
|
+
if (n.byteLength === 0) return from
|
|
383
390
|
const first = n[0]
|
|
384
391
|
let i = hay.indexOf(first, from)
|
|
385
392
|
while (i !== -1 && i <= last) {
|