@thomasfosterau/effect-parse 0.4.0 → 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 +40 -14
- 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 +2 -2
- package/dist/ParseError.d.ts.map +1 -1
- package/dist/ParseError.js +1 -1
- package/dist/ParseError.js.map +1 -1
- package/dist/Parser.d.ts +370 -28
- 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 +2 -2
- package/dist/ParserSchema.js.map +1 -1
- package/dist/ParserStream.d.ts +3 -2
- package/dist/ParserStream.d.ts.map +1 -1
- package/dist/ParserStream.js +15 -10
- 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 +1 -2
- 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 +9 -4
- package/src/Parser.ts +682 -129
- package/src/ParserSchema.ts +1 -1
- package/src/ParserStream.ts +40 -19
- 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 +10 -4
- 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/ParserSchema.ts
CHANGED
|
@@ -431,7 +431,7 @@ const isAnnotated = (ast: SchemaAST.AST): boolean => lookup(ast, annotationKey)
|
|
|
431
431
|
|
|
432
432
|
const withStop = <T>(ctx: Context<T>, text: string | undefined): Context<T> => {
|
|
433
433
|
if (text === undefined || text === "" || ctx.stop.includes(text[0])) return ctx
|
|
434
|
-
return { ...ctx, stop: Arr.sort([...ctx.stop, text[0]], Order.String).join("") }
|
|
434
|
+
return { ...ctx, stop: Arr.sort([...ctx.stop.split(""), text[0]], Order.String).join("") }
|
|
435
435
|
}
|
|
436
436
|
|
|
437
437
|
const at = <T>(ctx: Context<T>, key: PropertyKey): Context<T> => ({
|
package/src/ParserStream.ts
CHANGED
|
@@ -47,19 +47,31 @@ import type { Parser } from "./Parser.js"
|
|
|
47
47
|
*/
|
|
48
48
|
export type Chunkable = string | Uint8Array | ReadonlyArray<unknown>
|
|
49
49
|
|
|
50
|
-
const concat = <I extends Chunkable>(
|
|
51
|
-
|
|
50
|
+
const concat = <I extends Chunkable>(first: I, b: I): I => {
|
|
51
|
+
// Widened so the checks below narrow it (a type parameter does not narrow).
|
|
52
|
+
const a: Chunkable = first
|
|
53
|
+
if (Predicate.isString(a)) {
|
|
54
|
+
// SAFETY: both are chunks of one stream of `I`, so `b` is a string when `a` is, and so is their concatenation
|
|
55
|
+
return (a + (b as string)) as I
|
|
56
|
+
}
|
|
52
57
|
if (a instanceof Uint8Array) {
|
|
53
58
|
if (a.byteLength === 0) return b
|
|
59
|
+
// SAFETY: both are chunks of one stream of `I`, so `b` is a `Uint8Array` when `a` is
|
|
54
60
|
const out = new Uint8Array(a.length + (b as Uint8Array).length)
|
|
55
61
|
out.set(a)
|
|
62
|
+
// SAFETY: both are chunks of one stream of `I`, so `b` is a `Uint8Array` when `a` is
|
|
56
63
|
out.set(b as Uint8Array, a.length)
|
|
64
|
+
// SAFETY: `I` is `a`'s type, a `Uint8Array` here, and `out` is a new `Uint8Array`
|
|
57
65
|
return out as I
|
|
58
66
|
}
|
|
59
|
-
|
|
67
|
+
// SAFETY: both are chunks of one stream of `I`, so `b` is an array when `a` is
|
|
68
|
+
const joined: Chunkable = [...a, ...(b as ReadonlyArray<unknown>)]
|
|
69
|
+
// SAFETY: `I` is `a`'s type, an array here, and `joined` is a new array of both chunks' elements
|
|
70
|
+
return joined as I
|
|
60
71
|
}
|
|
61
72
|
|
|
62
73
|
const sliceFrom = <I extends Chunkable>(input: I, offset: number): I =>
|
|
74
|
+
// SAFETY: slicing a string, `Uint8Array` or array returns a value of the same kind
|
|
63
75
|
(Predicate.isString(input) ? input.slice(offset) : input.slice(offset)) as I
|
|
64
76
|
|
|
65
77
|
const noProgress = (input: Input, offset: number) =>
|
|
@@ -90,8 +102,9 @@ export interface ParseManyOptions<I> {
|
|
|
90
102
|
*
|
|
91
103
|
* A parser compiled with the `Compiler` module runs compiled, with the
|
|
92
104
|
* build that handles partial input (see `Parser.run`); a value that fails
|
|
93
|
-
*
|
|
94
|
-
* `Incomplete`
|
|
105
|
+
* is parsed again interpreted, so errors are the interpreter's. A value
|
|
106
|
+
* cut short by its chunk is not: compiled code reports `Incomplete` where
|
|
107
|
+
* the interpreter does.
|
|
95
108
|
*
|
|
96
109
|
* @since 0.1.0
|
|
97
110
|
* @category combinators
|
|
@@ -126,9 +139,12 @@ export const parseMany: {
|
|
|
126
139
|
let offset = 0
|
|
127
140
|
while (offset < input.length) {
|
|
128
141
|
state.reset(offset, { partial, context })
|
|
129
|
-
|
|
142
|
+
// A compiled `Incomplete` is the interpreter's: only an error needs the interpreted run
|
|
143
|
+
const a = Run.runOn(parser, step, state, false)
|
|
130
144
|
if (a === Fail) {
|
|
131
145
|
if (partial && state.mode === "Incomplete") break
|
|
146
|
+
state.reset(offset, { partial, context })
|
|
147
|
+
Run.runOn(parser, step, state)
|
|
132
148
|
return Effect.fail(ParseError.fromFailure<E>(input, state.reportedFailure(), state.mode, state.encoding))
|
|
133
149
|
}
|
|
134
150
|
if (state.pos === offset) return Effect.fail(noProgress(input, offset))
|
|
@@ -211,15 +227,19 @@ class Incremental<A, E, I extends Chunkable> {
|
|
|
211
227
|
}
|
|
212
228
|
|
|
213
229
|
private input(): I {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
230
|
+
if (this.kind === 1) {
|
|
231
|
+
// SAFETY: `kind` 1 is set by the first chunk being a `Uint8Array`, so `I` is `Uint8Array`
|
|
232
|
+
return this.bytes.subarray(0, this.length) as I
|
|
233
|
+
}
|
|
234
|
+
if (this.kind === 2) {
|
|
235
|
+
// Items are only appended between attempts, after each state has read its `end`
|
|
236
|
+
const items: Chunkable = this.items
|
|
237
|
+
// SAFETY: `kind` 2 is set by the first chunk being an array, so `I` is an array of the items' type
|
|
238
|
+
return items as I
|
|
222
239
|
}
|
|
240
|
+
// SAFETY: `kind` 0 means the first chunk was a string, so `I` is `string`; while `kind` is unset (no
|
|
241
|
+
// chunk arrived) `I` is unknown and the empty string stands for the empty input
|
|
242
|
+
return this.text as I
|
|
223
243
|
}
|
|
224
244
|
|
|
225
245
|
push(chunk: I): void {
|
|
@@ -245,8 +265,9 @@ class Incremental<A, E, I extends Chunkable> {
|
|
|
245
265
|
* The step attempts run: the parser's compiled code if it was compiled
|
|
246
266
|
* (the resumable build, unless `resumable` is false), else the interpreter.
|
|
247
267
|
*/
|
|
248
|
-
private step(state: State<I>, resumable = true):
|
|
249
|
-
|
|
268
|
+
private step(state: State<I>, resumable = true): A | Fail {
|
|
269
|
+
// SAFETY: a compiled step (typed `any` by the compilers) returns what the parser's own step returns
|
|
270
|
+
return Stack.run(Registry.stepFor(this.parser, state, resumable) ?? this.parser.step, state) as A | Fail
|
|
250
271
|
}
|
|
251
272
|
|
|
252
273
|
private attempt(): void {
|
|
@@ -255,7 +276,7 @@ class Incremental<A, E, I extends Chunkable> {
|
|
|
255
276
|
const a = this.step(state)
|
|
256
277
|
this.attempted = this.length
|
|
257
278
|
if (a !== Fail) {
|
|
258
|
-
this.settled = { value: a
|
|
279
|
+
this.settled = { value: a, pos: state.pos }
|
|
259
280
|
} else if (state.mode === "Incomplete") {
|
|
260
281
|
this.restart = state.failure.offset
|
|
261
282
|
} else {
|
|
@@ -274,7 +295,7 @@ class Incremental<A, E, I extends Chunkable> {
|
|
|
274
295
|
const resumable = this.attempted > 0
|
|
275
296
|
if (resumable) state.resume = this.resume
|
|
276
297
|
const a = this.step(state, resumable)
|
|
277
|
-
if (a !== Fail && state.pos === state.end) return Effect.succeed(a
|
|
298
|
+
if (a !== Fail && state.pos === state.end) return Effect.succeed(a)
|
|
278
299
|
}
|
|
279
300
|
// The error comes from an ordinary parse of the whole input, so it is the
|
|
280
301
|
// same as if the input had been parsed in one go
|
|
@@ -329,7 +350,7 @@ export const parseAll: {
|
|
|
329
350
|
Effect.flatMap(Effect.context<R>(), (context) =>
|
|
330
351
|
Effect.suspend(() => {
|
|
331
352
|
const empty = options?.empty === undefined ? undefined : fromSource<I>(options.empty)
|
|
332
|
-
const incremental = new Incremental(parser, context
|
|
353
|
+
const incremental = new Incremental(parser, context, empty)
|
|
333
354
|
return Effect.flatMap(
|
|
334
355
|
Stream.runForEach(self, (chunk) => Effect.sync(() => incremental.push(fromSource<I>(chunk)))),
|
|
335
356
|
() => Effect.suspend(() => incremental.finish())
|
package/src/Syntax.ts
CHANGED
|
@@ -66,11 +66,15 @@ import { described, isParser } from "./Parser.js"
|
|
|
66
66
|
// Grammar annotations
|
|
67
67
|
// -----------------------------------------------------------------------------
|
|
68
68
|
|
|
69
|
-
const syntaxNode =
|
|
69
|
+
const syntaxNode = <A, E, R, I extends Input>(
|
|
70
|
+
self: Parser<A, E, R, I>,
|
|
71
|
+
kind: string,
|
|
72
|
+
named: boolean
|
|
73
|
+
): Parser<A, E, R, I> => {
|
|
70
74
|
const step = self.step
|
|
71
75
|
// Identifies the nodes this parser builds, so a reparse can reuse them
|
|
72
76
|
const key = new Key()
|
|
73
|
-
return described<
|
|
77
|
+
return described<A, E, R, I>(
|
|
74
78
|
(state) => {
|
|
75
79
|
const builder = state.syntax
|
|
76
80
|
return builder === undefined ? step(state) : builder.node(state, step, key, kind, named)
|
|
@@ -101,10 +105,8 @@ const syntaxNode = (self: Parser.Any, kind: string, named: boolean): Parser.Any
|
|
|
101
105
|
export const node: {
|
|
102
106
|
(kind: string): <A, E, R, I extends Input>(self: Parser<A, E, R, I>) => Parser<A, E, R, I>
|
|
103
107
|
<A, E, R, I extends Input>(self: Parser<A, E, R, I>, kind: string): Parser<A, E, R, I>
|
|
104
|
-
} = /*#__PURE__*/ dual(
|
|
105
|
-
|
|
106
|
-
<A, E, R, I extends Input>(self: Parser<A, E, R, I>, kind: string): Parser<A, E, R, I> =>
|
|
107
|
-
syntaxNode(self, kind, true) as Parser<A, E, R, I>
|
|
108
|
+
} = /*#__PURE__*/ dual(2, <A, E, R, I extends Input>(self: Parser<A, E, R, I>, kind: string): Parser<A, E, R, I> =>
|
|
109
|
+
syntaxNode(self, kind, true)
|
|
108
110
|
)
|
|
109
111
|
|
|
110
112
|
/**
|
|
@@ -130,7 +132,7 @@ export const token: {
|
|
|
130
132
|
} = /*#__PURE__*/ dual(
|
|
131
133
|
(args) => isParser(args[0]),
|
|
132
134
|
<A, E, R, I extends Input>(self: Parser<A, E, R, I>, kind?: string): Parser<A, E, R, I> =>
|
|
133
|
-
syntaxNode(self, kind ?? defaultTokenKind(self), false)
|
|
135
|
+
syntaxNode(self, kind ?? defaultTokenKind(self), false)
|
|
134
136
|
)
|
|
135
137
|
|
|
136
138
|
const defaultTokenKind = (self: Parser.Any): string => {
|
|
@@ -168,7 +170,7 @@ export const field: {
|
|
|
168
170
|
return described<A, E, R, I>(
|
|
169
171
|
(state) => {
|
|
170
172
|
const builder = state.syntax
|
|
171
|
-
return
|
|
173
|
+
return builder === undefined ? step(state) : builder.field(state, step, name)
|
|
172
174
|
},
|
|
173
175
|
self.name,
|
|
174
176
|
{ _tag: "Syntax", self, kind: undefined, named: false }
|
|
@@ -346,6 +348,18 @@ export class Tree<out A, out E = never, I extends Input = string> {
|
|
|
346
348
|
}
|
|
347
349
|
}
|
|
348
350
|
|
|
351
|
+
/**
|
|
352
|
+
* What `JSON.stringify` writes for a {@link SyntaxNode}: its kind and span.
|
|
353
|
+
*
|
|
354
|
+
* @since 0.5.0
|
|
355
|
+
* @category models
|
|
356
|
+
*/
|
|
357
|
+
export interface SyntaxNodeJson {
|
|
358
|
+
readonly kind: string
|
|
359
|
+
readonly start: number
|
|
360
|
+
readonly end: number
|
|
361
|
+
}
|
|
362
|
+
|
|
349
363
|
/**
|
|
350
364
|
* A node of a syntax tree: its kind, span and value, and its place in the
|
|
351
365
|
* tree. Nodes are views built on demand; two views of the same node are
|
|
@@ -403,10 +417,11 @@ export class SyntaxNode<I extends Input = string> {
|
|
|
403
417
|
|
|
404
418
|
/** The input the node spans. */
|
|
405
419
|
get text(): Slice<I> {
|
|
406
|
-
const input = this.tree.input
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
: input.slice(this.start, this.end)
|
|
420
|
+
const input: Input = this.tree.input
|
|
421
|
+
// SAFETY: a `subarray` of a `Uint8Array`, or a `slice` of a string or array, has the input's own kind, `Slice<I>`
|
|
422
|
+
return (
|
|
423
|
+
input instanceof Uint8Array ? input.subarray(this.start, this.end) : input.slice(this.start, this.end)
|
|
424
|
+
) as Slice<I>
|
|
410
425
|
}
|
|
411
426
|
|
|
412
427
|
/** Whether this is an `ERROR` node. */
|
|
@@ -592,7 +607,7 @@ export class SyntaxNode<I extends Input = string> {
|
|
|
592
607
|
return out
|
|
593
608
|
}
|
|
594
609
|
|
|
595
|
-
toJSON():
|
|
610
|
+
toJSON(): SyntaxNodeJson {
|
|
596
611
|
return { kind: this.kind, start: this.start, end: this.end }
|
|
597
612
|
}
|
|
598
613
|
|
|
@@ -723,6 +738,8 @@ const run = <A, E, I extends Input>(config: Config, input: I, previous: Previous
|
|
|
723
738
|
state.backtrack("Eof", expectedEof)
|
|
724
739
|
fatal = { failure: state.reportedFailure(), mode: "Backtrack" }
|
|
725
740
|
} else {
|
|
741
|
+
// SAFETY: `config.inner` is the `Parser<A, ...>` given to `parse` or the parser its root `node` wraps, which returns
|
|
742
|
+
// the same `A` (a `reparse` reuses the tree's config)
|
|
726
743
|
value = Option.some(a as A)
|
|
727
744
|
}
|
|
728
745
|
const green = builder.finish(config.kind, Option.getOrUndefined(value), fatal, state.pos)
|
|
@@ -730,19 +747,12 @@ const run = <A, E, I extends Input>(config: Config, input: I, previous: Previous
|
|
|
730
747
|
}
|
|
731
748
|
|
|
732
749
|
/**
|
|
733
|
-
*
|
|
734
|
-
* tree's `value`, and its {@link node}s are the tree's nodes. When the
|
|
735
|
-
* grammar is itself a {@link node}, that node is the root; otherwise the
|
|
736
|
-
* root is a node of kind `options.kind` (by default `"document"`).
|
|
737
|
-
*
|
|
738
|
-
* The parse never fails: recovered failures (see `Parser.resumeAfter`), a
|
|
739
|
-
* fatal failure and unparsed input at the end all become `ERROR` nodes, and
|
|
740
|
-
* are listed in `tree.errors`. The parse is interpreted, never compiled.
|
|
750
|
+
* The overloads of {@link parse}.
|
|
741
751
|
*
|
|
742
|
-
* @since 0.
|
|
752
|
+
* @since 0.5.0
|
|
743
753
|
* @category parsing
|
|
744
754
|
*/
|
|
745
|
-
export
|
|
755
|
+
export interface ParseFn {
|
|
746
756
|
<A, E, I extends Input>(
|
|
747
757
|
grammar: Parser<A, E, never, I>,
|
|
748
758
|
input: NoInfer<Source<I>>,
|
|
@@ -753,7 +763,22 @@ export const parse: {
|
|
|
753
763
|
input: NoInfer<Source<I>>,
|
|
754
764
|
options: ParseOptionsWithContext<R>
|
|
755
765
|
): Tree<A, E, I>
|
|
756
|
-
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
/**
|
|
769
|
+
* Parses an input into a syntax {@link Tree}. The grammar's output is the
|
|
770
|
+
* tree's `value`, and its {@link node}s are the tree's nodes. When the
|
|
771
|
+
* grammar is itself a {@link node}, that node is the root; otherwise the
|
|
772
|
+
* root is a node of kind `options.kind` (by default `"document"`).
|
|
773
|
+
*
|
|
774
|
+
* The parse never fails: recovered failures (see `Parser.resumeAfter`), a
|
|
775
|
+
* fatal failure and unparsed input at the end all become `ERROR` nodes, and
|
|
776
|
+
* are listed in `tree.errors`. The parse is interpreted, never compiled.
|
|
777
|
+
*
|
|
778
|
+
* @since 0.2.0
|
|
779
|
+
* @category parsing
|
|
780
|
+
*/
|
|
781
|
+
export const parse: ParseFn = <A, E, R, I extends Input>(
|
|
757
782
|
grammar: Parser<A, E, R, I>,
|
|
758
783
|
input: I | Source<I>,
|
|
759
784
|
options?: ParseOptions & { readonly context?: Context.Context<R> }
|
|
@@ -762,11 +787,12 @@ export const parse: {
|
|
|
762
787
|
const isRoot = Predicate.isTagged(n, "Syntax") && n.kind !== undefined && n.named
|
|
763
788
|
const config: Config = {
|
|
764
789
|
kind: isRoot ? n.kind : (options?.kind ?? "document"),
|
|
790
|
+
// SAFETY: a `Syntax` node's `self` is the parser that `node` wrapped, a parser built by this library
|
|
765
791
|
inner: isRoot ? (n.self as Parser.Any) : grammar,
|
|
766
792
|
lookahead: options?.lookahead ?? 0,
|
|
767
|
-
context: options?.context
|
|
793
|
+
context: options?.context
|
|
768
794
|
}
|
|
769
|
-
return run(config, fromSource(input
|
|
795
|
+
return run(config, fromSource<I>(input), undefined)
|
|
770
796
|
}
|
|
771
797
|
|
|
772
798
|
/**
|
|
@@ -803,13 +829,8 @@ export const reparse = <A, E, I extends Input>(
|
|
|
803
829
|
input: NoInfer<Source<I>>,
|
|
804
830
|
edits?: Edit | ReadonlyArray<Edit>
|
|
805
831
|
): Tree<A, E, I> => {
|
|
806
|
-
const next = fromSource(input
|
|
807
|
-
const list =
|
|
808
|
-
edits === undefined
|
|
809
|
-
? diffList(tree.input, next)
|
|
810
|
-
: "start" in edits
|
|
811
|
-
? [edits as Edit]
|
|
812
|
-
: (edits as ReadonlyArray<Edit>)
|
|
832
|
+
const next = fromSource<I>(input)
|
|
833
|
+
const list = edits === undefined ? diffList(tree.input, next) : "start" in edits ? [edits] : edits
|
|
813
834
|
const previous: Previous = {
|
|
814
835
|
root: tree.green,
|
|
815
836
|
oldLength: tree.input.length,
|
|
@@ -854,9 +875,12 @@ export const replace = <A, E>(
|
|
|
854
875
|
* @category parsing
|
|
855
876
|
*/
|
|
856
877
|
export const diff = <I extends Input>(before: I, after: I): Edit | undefined => {
|
|
857
|
-
const a = before
|
|
858
|
-
const b = after
|
|
859
|
-
|
|
878
|
+
const a: Input = before
|
|
879
|
+
const b: Input = after
|
|
880
|
+
// SAFETY: the string reader is chosen only when `a` is a string, and `b` has the same type `I`
|
|
881
|
+
const at = Predicate.isString(a)
|
|
882
|
+
? (s: Input, i: number) => (s as string).charCodeAt(i)
|
|
883
|
+
: (s: Input, i: number) => s[i]
|
|
860
884
|
const max = Math.min(a.length, b.length)
|
|
861
885
|
let prefix = 0
|
|
862
886
|
while (prefix < max && at(a, prefix) === at(b, prefix)) prefix++
|
package/src/Text.ts
CHANGED
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
import * as Predicate from "effect/Predicate"
|
|
40
40
|
import type { Encoding } from "./Encoding.js"
|
|
41
41
|
import * as Encodings from "./Encoding.js"
|
|
42
|
-
import type {
|
|
42
|
+
import type { State } from "./Input.js"
|
|
43
43
|
import { Fail } from "./Input.js"
|
|
44
44
|
import { escapedImpl, overflow, takeEscapedImpl, toFloat } from "./Ascii.js"
|
|
45
45
|
import { codePointPredicate, compile, indexOf, matchLiteral } from "./internal/tokens.js"
|
|
@@ -392,7 +392,7 @@ const expectedIdentifier = ["identifier"]
|
|
|
392
392
|
* @category constructors
|
|
393
393
|
*/
|
|
394
394
|
export const make = <I extends string | Uint8Array>(encoding: Encoding<I>): TextParsers<I> => {
|
|
395
|
-
const enc = encoding
|
|
395
|
+
const enc = encoding
|
|
396
396
|
const fast = internal.fastLimit(enc)
|
|
397
397
|
|
|
398
398
|
// Each parser records its encoding on byte input (`state.encoding`), so
|
|
@@ -410,7 +410,7 @@ export const make = <I extends string | Uint8Array>(encoding: Encoding<I>): Text
|
|
|
410
410
|
): string | Fail => {
|
|
411
411
|
const count = internal.validate(enc, state, start, end)
|
|
412
412
|
if (count < 0) return backtrack(state, kind, expected, -1 - count)
|
|
413
|
-
return
|
|
413
|
+
return internal.textIn(enc, state, start, end)
|
|
414
414
|
}
|
|
415
415
|
|
|
416
416
|
const takeWhileText = (
|
|
@@ -438,7 +438,7 @@ export const make = <I extends string | Uint8Array>(encoding: Encoding<I>): Text
|
|
|
438
438
|
// More matching characters would have been accepted where we stopped
|
|
439
439
|
if (hint && count < max) state.setHint(offset, expected)
|
|
440
440
|
state.pos = offset
|
|
441
|
-
return
|
|
441
|
+
return internal.textIn(enc, state, start, offset)
|
|
442
442
|
},
|
|
443
443
|
name,
|
|
444
444
|
{ _tag: "TakeWhile", set, negate, min, max, encoding: enc }
|
|
@@ -471,6 +471,7 @@ export const make = <I extends string | Uint8Array>(encoding: Encoding<I>): Text
|
|
|
471
471
|
if (state.inputKind === 1) state.encoding = enc
|
|
472
472
|
if (pos >= state.end) return state.eoi("Literal", expected, 1)
|
|
473
473
|
const input = state.input
|
|
474
|
+
// SAFETY: a `Text` parser runs on strings or `Uint8Array`s, and `inputKind` 0 is set only for a string
|
|
474
475
|
if ((state.inputKind === 0 ? (input as string).charCodeAt(pos) : (input as Uint8Array)[pos]) !== unit) {
|
|
475
476
|
return backtrack(state, "Literal", expected)
|
|
476
477
|
}
|
|
@@ -518,7 +519,7 @@ export const make = <I extends string | Uint8Array>(encoding: Encoding<I>): Text
|
|
|
518
519
|
pos += internal.width
|
|
519
520
|
}
|
|
520
521
|
state.pos = pos
|
|
521
|
-
return
|
|
522
|
+
return internal.textIn(enc, state, start, pos)
|
|
522
523
|
},
|
|
523
524
|
expected[0],
|
|
524
525
|
{ _tag: "Literal", literal: lit, caseless: true, encoding: enc }
|
|
@@ -559,7 +560,7 @@ export const make = <I extends string | Uint8Array>(encoding: Encoding<I>): Text
|
|
|
559
560
|
pos += internal.width
|
|
560
561
|
}
|
|
561
562
|
state.pos = pos
|
|
562
|
-
return
|
|
563
|
+
return internal.textIn(enc, state, start, pos)
|
|
563
564
|
},
|
|
564
565
|
"take",
|
|
565
566
|
{ _tag: "Take", count, encoding: enc }
|
|
@@ -575,9 +576,9 @@ export const make = <I extends string | Uint8Array>(encoding: Encoding<I>): Text
|
|
|
575
576
|
(state: State<any>) => {
|
|
576
577
|
if (state.inputKind === 1) state.encoding = enc
|
|
577
578
|
const start = state.pos
|
|
578
|
-
let idx = indexOf(state, needle
|
|
579
|
+
let idx = indexOf(state, needle, start)
|
|
579
580
|
// Multi-unit encodings: only accept matches on a character boundary
|
|
580
|
-
while (unit > 1 && idx >= 0 && (idx - start) % unit !== 0) idx = indexOf(state, needle
|
|
581
|
+
while (unit > 1 && idx >= 0 && (idx - start) % unit !== 0) idx = indexOf(state, needle, idx + 1)
|
|
581
582
|
if (idx < 0) {
|
|
582
583
|
if (state.partial) return state.incomplete(0, expected)
|
|
583
584
|
return backtrack(state, "TakeUntil", expected, state.end)
|
|
@@ -590,7 +591,7 @@ export const make = <I extends string | Uint8Array>(encoding: Encoding<I>): Text
|
|
|
590
591
|
if (count < min) return backtrack(state, "TakeUntil", expected, idx)
|
|
591
592
|
if (count > max) return backtrack(state, "TakeUntil", expected)
|
|
592
593
|
state.pos = idx
|
|
593
|
-
return
|
|
594
|
+
return internal.textIn(enc, state, start, idx)
|
|
594
595
|
},
|
|
595
596
|
"takeUntil",
|
|
596
597
|
{ _tag: "TakeUntil", literal: lit, min, max, encoding: enc }
|
|
@@ -607,7 +608,7 @@ export const make = <I extends string | Uint8Array>(encoding: Encoding<I>): Text
|
|
|
607
608
|
if (internal.scanStop === internal.Truncated) return state.incomplete(1, expectedAny)
|
|
608
609
|
if (internal.scanStop === internal.Invalid) return backtrack(state, "Token", expectedAny, offset)
|
|
609
610
|
state.pos = offset
|
|
610
|
-
return
|
|
611
|
+
return internal.textIn(enc, state, start, offset)
|
|
611
612
|
},
|
|
612
613
|
"rest",
|
|
613
614
|
{ _tag: "Rest", encoding: enc }
|
|
@@ -628,7 +629,7 @@ export const make = <I extends string | Uint8Array>(encoding: Encoding<I>): Text
|
|
|
628
629
|
}
|
|
629
630
|
|
|
630
631
|
const cls = (test: CharPredicate, min: 0 | 1, description: string, kind: ErrorKind, hint = true) =>
|
|
631
|
-
takeWhileText(test
|
|
632
|
+
takeWhileText(test, { min }, false, `${kind.toLowerCase()}${min}`, [description], kind, hint)
|
|
632
633
|
|
|
633
634
|
const identifierStart = (cp: number) => cp === 95 || isIdStart(cp)
|
|
634
635
|
const identifier: Parser<string, never, never, I> = P.described<string, never, never, I>(
|
|
@@ -638,13 +639,13 @@ export const make = <I extends string | Uint8Array>(encoding: Encoding<I>): Text
|
|
|
638
639
|
const cp = internal.next(enc, state)
|
|
639
640
|
if (cp < 0) return internal.failAt(state, cp, "TakeWhile", expectedIdentifier)
|
|
640
641
|
if (!identifierStart(cp)) return state.backtrack("TakeWhile", expectedIdentifier)
|
|
641
|
-
internal.scan(enc, state, isIdContinue
|
|
642
|
+
internal.scan(enc, state, isIdContinue, false, Infinity, start + internal.width, fast)
|
|
642
643
|
const stop = internal.scanStop
|
|
643
644
|
if (state.partial && (stop === internal.End || stop === internal.Truncated)) {
|
|
644
645
|
return state.incomplete(1, expectedIdentifier)
|
|
645
646
|
}
|
|
646
647
|
state.pos = internal.scanOffset
|
|
647
|
-
return
|
|
648
|
+
return internal.textIn(enc, state, start, state.pos)
|
|
648
649
|
},
|
|
649
650
|
"identifier",
|
|
650
651
|
{ _tag: "Identifier", encoding: enc }
|
|
@@ -691,7 +692,7 @@ export const make = <I extends string | Uint8Array>(encoding: Encoding<I>): Text
|
|
|
691
692
|
if (after !== 10) return backtrack(state, "LineEnding", expectedLineEnding, offset)
|
|
692
693
|
}
|
|
693
694
|
state.pos = offset
|
|
694
|
-
return
|
|
695
|
+
return internal.textIn(enc, state, start, offset)
|
|
695
696
|
},
|
|
696
697
|
"tillLineEnding",
|
|
697
698
|
{ _tag: "TillLineEnding", encoding: enc }
|
|
@@ -783,7 +784,7 @@ export const make = <I extends string | Uint8Array>(encoding: Encoding<I>): Text
|
|
|
783
784
|
normal,
|
|
784
785
|
matchControl(control),
|
|
785
786
|
escapable,
|
|
786
|
-
(state, start) =>
|
|
787
|
+
(state, start) => internal.textIn(enc, state, start, state.pos),
|
|
787
788
|
internal.firstCodePoint(control),
|
|
788
789
|
enc
|
|
789
790
|
),
|