@thomasfosterau/effect-parse 0.4.1 → 0.6.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/CHANGELOG.md +202 -0
- package/README.md +56 -14
- package/dist/Ascii.d.ts +11 -4
- package/dist/Ascii.d.ts.map +1 -1
- package/dist/Ascii.js +12 -4
- package/dist/Ascii.js.map +1 -1
- package/dist/Binary.d.ts +5 -2
- package/dist/Binary.d.ts.map +1 -1
- package/dist/Binary.js +83 -37
- package/dist/Binary.js.map +1 -1
- package/dist/Bits.d.ts.map +1 -1
- package/dist/Bits.js +11 -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 +56 -5
- package/dist/Input.d.ts.map +1 -1
- package/dist/Input.js +70 -1
- 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 +69 -11
- package/dist/ParseError.d.ts.map +1 -1
- package/dist/ParseError.js +129 -28
- package/dist/ParseError.js.map +1 -1
- package/dist/Parser.d.ts +375 -29
- package/dist/Parser.d.ts.map +1 -1
- package/dist/Parser.js +394 -37
- package/dist/Parser.js.map +1 -1
- package/dist/ParserSchema.d.ts.map +1 -1
- package/dist/ParserSchema.js +2 -3
- package/dist/ParserSchema.js.map +1 -1
- package/dist/ParserStream.d.ts +122 -14
- package/dist/ParserStream.d.ts.map +1 -1
- package/dist/ParserStream.js +293 -62
- 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 +1 -1
- package/dist/Syntax.js.map +1 -1
- package/dist/Text.d.ts.map +1 -1
- package/dist/Text.js +21 -15
- package/dist/Text.js.map +1 -1
- package/dist/Token.d.ts +65 -11
- package/dist/Token.d.ts.map +1 -1
- package/dist/Token.js +73 -26
- package/dist/Token.js.map +1 -1
- package/dist/internal/compiler/closure.d.ts +1 -1
- package/dist/internal/compiler/closure.d.ts.map +1 -1
- package/dist/internal/compiler/closure.js +263 -113
- 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 +9 -2
- package/dist/internal/compiler/first.js.map +1 -1
- package/dist/internal/compiler/graph.d.ts +39 -0
- package/dist/internal/compiler/graph.d.ts.map +1 -0
- package/dist/internal/compiler/graph.js +78 -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 +208 -106
- package/dist/internal/compiler/jit.js.map +1 -1
- package/dist/internal/compiler/registry.d.ts +11 -3
- 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 +2840 -0
- package/dist/internal/compiler/wasm.js.map +1 -0
- package/dist/internal/compiler/wasmAsm.d.ts +199 -0
- package/dist/internal/compiler/wasmAsm.d.ts.map +1 -0
- package/dist/internal/compiler/wasmAsm.js +427 -0
- package/dist/internal/compiler/wasmAsm.js.map +1 -0
- package/dist/internal/entries.d.ts +3 -1
- package/dist/internal/entries.d.ts.map +1 -1
- package/dist/internal/entries.js.map +1 -1
- package/dist/internal/node.d.ts +77 -3
- package/dist/internal/node.d.ts.map +1 -1
- package/dist/internal/node.js +54 -10
- 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 +14 -0
- package/dist/internal/run.d.ts.map +1 -1
- package/dist/internal/run.js +53 -11
- package/dist/internal/run.js.map +1 -1
- package/dist/internal/stack.d.ts +14 -0
- package/dist/internal/stack.d.ts.map +1 -1
- package/dist/internal/stack.js +24 -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 +20 -0
- package/dist/internal/tokens.d.ts.map +1 -1
- package/dist/internal/tokens.js +72 -39
- package/dist/internal/tokens.js.map +1 -1
- package/dist/internal/tuple.d.ts +6 -0
- package/dist/internal/tuple.d.ts.map +1 -0
- package/dist/internal/tuple.js +170 -0
- package/dist/internal/tuple.js.map +1 -0
- package/package.json +18 -10
- package/src/Ascii.ts +47 -12
- package/src/Binary.ts +174 -73
- package/src/Bits.ts +16 -6
- package/src/Compiler.ts +101 -10
- package/src/Expression.ts +94 -81
- package/src/Input.ts +99 -7
- package/src/Nom.ts +15 -5
- package/src/ParseError.ts +209 -52
- package/src/Parser.ts +684 -143
- package/src/ParserSchema.ts +1 -2
- package/src/ParserStream.ts +361 -68
- package/src/Syntax.ts +60 -36
- package/src/Text.ts +32 -27
- package/src/Token.ts +152 -34
- package/src/internal/compiler/closure.ts +371 -153
- package/src/internal/compiler/entries.ts +4 -2
- package/src/internal/compiler/first.ts +16 -0
- package/src/internal/compiler/graph.ts +96 -0
- package/src/internal/compiler/jit.ts +319 -169
- 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 +3276 -0
- package/src/internal/compiler/wasmAsm.ts +484 -0
- package/src/internal/entries.ts +8 -2
- package/src/internal/node.ts +103 -7
- package/src/internal/numbers.ts +3 -1
- package/src/internal/run.ts +73 -12
- package/src/internal/stack.ts +25 -1
- package/src/internal/syntax.ts +39 -18
- package/src/internal/text.ts +97 -9
- package/src/internal/tokens.ts +106 -14
- package/src/internal/tuple.ts +141 -0
package/src/Compiler.ts
CHANGED
|
@@ -4,11 +4,15 @@
|
|
|
4
4
|
* `Parser.parseSync`, `Parser.parsePeek`, `Parser.parsePartial` and
|
|
5
5
|
* `Parser.parseEffect` faster, from any `offset`.
|
|
6
6
|
*
|
|
7
|
-
* There are
|
|
8
|
-
*
|
|
7
|
+
* There are three compilers, one function each, so a program bundles only
|
|
8
|
+
* the ones it uses, and a program that compiles nothing bundles none:
|
|
9
9
|
*
|
|
10
|
+
* - {@link wasm} scans the input in WebAssembly and builds the value with
|
|
11
|
+
* generated JavaScript. It is the fastest on strings and bytes, and uses
|
|
12
|
+
* the JIT for array input and streaming, and where WebAssembly or
|
|
13
|
+
* `new Function` is unavailable.
|
|
10
14
|
* - {@link jit} generates code with `new Function` where it is allowed, and
|
|
11
|
-
* otherwise uses the closure compiler.
|
|
15
|
+
* otherwise uses the closure compiler.
|
|
12
16
|
* - {@link closure} builds specialized closures only. It never generates
|
|
13
17
|
* code, and a bundle that uses only it contains no `new Function`, so it
|
|
14
18
|
* is the choice under a Content Security Policy, where even probing for
|
|
@@ -32,7 +36,9 @@
|
|
|
32
36
|
* **Results and errors are unchanged.** Compiled code records no
|
|
33
37
|
* diagnostics. When it fails, the parser runs again, interpreted, from the
|
|
34
38
|
* same offset, so every `ParseError` (offset, expected, context, cause and
|
|
35
|
-
* message) is exactly the one the interpreter reports.
|
|
39
|
+
* message) is exactly the one the interpreter reports. Running out of
|
|
40
|
+
* partial input is not a failure that needs it: compiled code reports
|
|
41
|
+
* `Incomplete` where the interpreter does, with the same `needed`.
|
|
36
42
|
*
|
|
37
43
|
* **Callbacks should be pure.** On an input that fails, the callbacks given
|
|
38
44
|
* to `map`, `filter`, `tryMap`, `tap`, `fold`, ... may run twice, once
|
|
@@ -41,19 +47,57 @@
|
|
|
41
47
|
* nor those of a branch that is certain to fail, and token-set predicates
|
|
42
48
|
* may be called once per character code and cached.
|
|
43
49
|
*
|
|
44
|
-
* `Parser.parsePartial
|
|
45
|
-
* handles partial input
|
|
46
|
-
*
|
|
50
|
+
* `Parser.parsePartial`, `ParserStream.parseAll` and
|
|
51
|
+
* `ParserStream.parseMany` run a build that handles partial input;
|
|
52
|
+
* `parseMany` runs it once per value and parses a value again, interpreted,
|
|
53
|
+
* only when it fails. `Parser.parseRecoverable`, `Syntax`
|
|
54
|
+
* parses and runs with the `trace` option are always interpreted. Parsers that
|
|
47
55
|
* can't be described, such as `Parser.make` and `Parser.gen`, run
|
|
48
|
-
* interpreted inside compiled code
|
|
56
|
+
* interpreted inside compiled code; the parsers given to a
|
|
57
|
+
* `Parser.combinator` compile, and its step is built from theirs.
|
|
49
58
|
*
|
|
50
59
|
* @since 0.2.0
|
|
51
60
|
*/
|
|
52
|
-
import type { Input } from "./Input.js"
|
|
53
|
-
import type { Parser } from "./Parser.js"
|
|
61
|
+
import type { Fail, Input, State } from "./Input.js"
|
|
62
|
+
import type { Parser, RunOptions } from "./Parser.js"
|
|
54
63
|
import { backend as closureBackend } from "./internal/compiler/closure.js"
|
|
55
64
|
import { backend as jitBackend } from "./internal/compiler/jit.js"
|
|
56
65
|
import { register } from "./internal/compiler/registry.js"
|
|
66
|
+
import { backend as wasmBackend } from "./internal/compiler/wasm.js"
|
|
67
|
+
import { prepare } from "./internal/run.js"
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Compiles a parser with the WebAssembly compiler. Returns the same parser.
|
|
71
|
+
*
|
|
72
|
+
* The input is copied into WebAssembly memory and scanned there, by code
|
|
73
|
+
* generated from the grammar; a JavaScript function generated with
|
|
74
|
+
* `new Function` then builds the value from what the scan recorded, with
|
|
75
|
+
* the same callbacks and the same objects as the JIT. Parts of the grammar
|
|
76
|
+
* with no WebAssembly form (such as `Parser.make`, `flatMap`, or `filter`
|
|
77
|
+
* on a value) run their JIT-compiled code from inside the scan.
|
|
78
|
+
*
|
|
79
|
+
* It uses the JIT (see {@link jit}) for array input, for the streaming build
|
|
80
|
+
* of `Parser.parsePartial` and `ParserStream`, and where `WebAssembly` or
|
|
81
|
+
* `new Function` is unavailable: a Content Security Policy needs both
|
|
82
|
+
* `'wasm-unsafe-eval'` and `'unsafe-eval'` for it. Short strings with the
|
|
83
|
+
* same characters may be returned as one string, which no program can tell
|
|
84
|
+
* apart from two.
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```ts
|
|
88
|
+
* import { Ascii, Compiler, Parser, Token } from "@thomasfosterau/effect-parse"
|
|
89
|
+
*
|
|
90
|
+
* const list = Compiler.wasm(Parser.separated(Ascii.decInt, Token.literal(",")))
|
|
91
|
+
* Parser.parse(list, "1,2,3") // Result.succeed([1, 2, 3])
|
|
92
|
+
* ```
|
|
93
|
+
*
|
|
94
|
+
* @since 0.5.0
|
|
95
|
+
* @category compilation
|
|
96
|
+
*/
|
|
97
|
+
export const wasm = <A, E, R, I extends Input>(self: Parser<A, E, R, I>): Parser<A, E, R, I> => {
|
|
98
|
+
register(self, wasmBackend)
|
|
99
|
+
return self
|
|
100
|
+
}
|
|
57
101
|
|
|
58
102
|
/**
|
|
59
103
|
* Compiles a parser with the JIT: generated code where `new Function` is
|
|
@@ -96,3 +140,50 @@ export const closure = <A, E, R, I extends Input>(self: Parser<A, E, R, I>): Par
|
|
|
96
140
|
register(self, closureBackend)
|
|
97
141
|
return self
|
|
98
142
|
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Prepares a parser for running many times on states you own, as
|
|
146
|
+
* `Parser.run` does: for drivers that make one small run per token, such as
|
|
147
|
+
* a lexer over a buffer. `Parser.run` looks the parser's compiled form up
|
|
148
|
+
* on every run, which costs about a quarter of a small token's time; the
|
|
149
|
+
* function returned here looks it up once (again only when the input kind
|
|
150
|
+
* changes, or between complete and partial states) and calls the compiled
|
|
151
|
+
* code directly.
|
|
152
|
+
*
|
|
153
|
+
* It returns exactly what `Parser.run(self, state, options)` returns, and
|
|
154
|
+
* leaves the state as it would: the output with the cursor where the
|
|
155
|
+
* parser stopped, or `Fail` with the interpreter's failure (or, with
|
|
156
|
+
* `diagnostics: false`, only the mode). Compile the parser first with
|
|
157
|
+
* {@link jit} or {@link closure}; a parser that isn't compiled runs
|
|
158
|
+
* interpreted. The compiled form is looked up on the first run, so compile
|
|
159
|
+
* the parser before using the runner: compiling it again later doesn't
|
|
160
|
+
* reach a runner that has already run.
|
|
161
|
+
*
|
|
162
|
+
* @example
|
|
163
|
+
* ```ts
|
|
164
|
+
* import { Ascii, Compiler, Input, Parser, Token } from "@thomasfosterau/effect-parse"
|
|
165
|
+
*
|
|
166
|
+
* const token = Compiler.jit(Parser.preceded(Ascii.space0, Parser.alt(Ascii.alpha1, Token.oneOf("+-"))))
|
|
167
|
+
* const next = Compiler.runner(token)
|
|
168
|
+
*
|
|
169
|
+
* const tokens = (text: string): Array<string> => {
|
|
170
|
+
* const state = new Input.State(text)
|
|
171
|
+
* const out: Array<string> = []
|
|
172
|
+
* while (state.pos < state.end) {
|
|
173
|
+
* const t = next(state.reset(state.pos))
|
|
174
|
+
* if (t === Input.Fail) break
|
|
175
|
+
* out.push(t)
|
|
176
|
+
* }
|
|
177
|
+
* return out
|
|
178
|
+
* }
|
|
179
|
+
*
|
|
180
|
+
* tokens("a + b") // ["a", "+", "b"]
|
|
181
|
+
* ```
|
|
182
|
+
*
|
|
183
|
+
* @since 0.5.0
|
|
184
|
+
* @category running
|
|
185
|
+
*/
|
|
186
|
+
export const runner = <A, E, R, I extends Input>(
|
|
187
|
+
self: Parser<A, E, R, I>,
|
|
188
|
+
options?: RunOptions
|
|
189
|
+
): ((state: State<I>) => A | Fail) => prepare(self, self.step, options?.diagnostics !== false)
|
package/src/Expression.ts
CHANGED
|
@@ -38,7 +38,10 @@ import * as Predicate from "effect/Predicate"
|
|
|
38
38
|
import type { Input, State } from "./Input.js"
|
|
39
39
|
import { Fail } from "./Input.js"
|
|
40
40
|
import type { Parser } from "./Parser.js"
|
|
41
|
-
import {
|
|
41
|
+
import { combinator } from "./Parser.js"
|
|
42
|
+
|
|
43
|
+
/** A parser's step, interpreted or compiled. */
|
|
44
|
+
type Step = (state: State<any>) => unknown
|
|
42
45
|
import { TiedFailures } from "./internal/ties.js"
|
|
43
46
|
|
|
44
47
|
/**
|
|
@@ -154,101 +157,111 @@ export const make = <A, E, R, I extends Input, const Ops extends ReadonlyArray<O
|
|
|
154
157
|
readonly operand: Parser<A, E, R, I>
|
|
155
158
|
readonly operators: Ops
|
|
156
159
|
}): Parser<A, E | OpError<Ops[number]>, R | OpServices<Ops[number]>, I & OpInput<Ops[number]>> => {
|
|
157
|
-
const operand = options.operand.step
|
|
158
160
|
const prefixes = options.operators.filter((o): o is Prefix<A, any, any, any> => Predicate.isTagged(o, "Prefix"))
|
|
159
161
|
const postfixes = options.operators.filter((o): o is Postfix<A, any, any, any> => Predicate.isTagged(o, "Postfix"))
|
|
160
162
|
const infixes = options.operators.filter((o): o is Infix<A, any, any, any> => Predicate.isTagged(o, "Infix"))
|
|
163
|
+
const operators = [...prefixes, ...postfixes, ...infixes]
|
|
164
|
+
const parsers = [options.operand, ...operators.map((o) => o.parser)]
|
|
165
|
+
// Builds the step from the operand's and the operators' steps, in that
|
|
166
|
+
// order: their own steps when interpreted, compiled ones when compiled
|
|
167
|
+
const build = (steps: ReadonlyArray<Step>) => {
|
|
168
|
+
const operand = steps[0]
|
|
169
|
+
const prefixSteps = steps.slice(1, 1 + prefixes.length)
|
|
170
|
+
const postfixSteps = steps.slice(1 + prefixes.length, 1 + prefixes.length + postfixes.length)
|
|
171
|
+
const infixSteps = steps.slice(1 + prefixes.length + postfixes.length)
|
|
161
172
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
173
|
+
// Attempts each operator in turn. Returns the index of the first match
|
|
174
|
+
// (with its output in `matched`), -1 if none matched, or -2 on a fatal
|
|
175
|
+
// failure. Failed attempts are folded into `failures`, merged only when
|
|
176
|
+
// read (#44): a round in which an operator matches discards them unmerged.
|
|
177
|
+
// Like `matched`, it is shared by nested expressions, which clear it.
|
|
178
|
+
let matched: unknown
|
|
179
|
+
const failures = new TiedFailures()
|
|
180
|
+
const tryOps = (state: State<any>, ops: ReadonlyArray<Step>, mustConsume: boolean): number => {
|
|
181
|
+
const start = state.pos
|
|
182
|
+
for (let i = 0; i < ops.length; i++) {
|
|
183
|
+
const out = ops[i](state)
|
|
184
|
+
if (out !== Fail) {
|
|
185
|
+
// A prefix or postfix operator matching nothing would apply forever
|
|
186
|
+
if (mustConsume && state.pos === start) {
|
|
187
|
+
state.failMessage("Cut", "Assert", "prefix and postfix operators must always consume")
|
|
188
|
+
return -2
|
|
189
|
+
}
|
|
190
|
+
matched = out
|
|
191
|
+
return i
|
|
178
192
|
}
|
|
179
|
-
|
|
180
|
-
|
|
193
|
+
if (state.mode !== "Backtrack") return -2
|
|
194
|
+
state.rewind(start)
|
|
195
|
+
failures.add(state)
|
|
181
196
|
}
|
|
182
|
-
|
|
183
|
-
state.rewind(start)
|
|
184
|
-
failures.add(state)
|
|
197
|
+
return -1
|
|
185
198
|
}
|
|
186
|
-
return -1
|
|
187
|
-
}
|
|
188
199
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
if (
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
200
|
+
const expr = (state: State<any>, minPower: number): A | Fail => {
|
|
201
|
+
let lhs: A | Fail
|
|
202
|
+
failures.clear()
|
|
203
|
+
const p = tryOps(state, prefixSteps, true)
|
|
204
|
+
if (p === -2) return Fail
|
|
205
|
+
if (p >= 0) {
|
|
206
|
+
const op = prefixes[p]
|
|
207
|
+
const opOut = matched
|
|
208
|
+
const operandValue = expr(state, op.precedence * 2)
|
|
209
|
+
if (operandValue === Fail) return Fail
|
|
210
|
+
lhs = op.fold(opOut, operandValue)
|
|
211
|
+
} else {
|
|
212
|
+
const prefixFailure = failures.failure
|
|
213
|
+
const prefixTie1 = failures.tie1
|
|
214
|
+
const prefixTie2 = failures.tie2
|
|
215
|
+
// SAFETY: the operand's step, interpreted or compiled, returns its output (an `A`) or `Fail`
|
|
216
|
+
lhs = operand(state) as A | Fail
|
|
217
|
+
if (lhs === Fail) {
|
|
218
|
+
if (prefixFailure !== undefined && state.mode === "Backtrack") {
|
|
219
|
+
// The operand may have parsed nested expressions, which reuse `failures`
|
|
220
|
+
failures.failure = prefixFailure
|
|
221
|
+
failures.tie1 = prefixTie1
|
|
222
|
+
failures.tie2 = prefixTie2
|
|
223
|
+
failures.add(state)
|
|
224
|
+
failures.fail(state)
|
|
225
|
+
}
|
|
226
|
+
return Fail
|
|
213
227
|
}
|
|
214
|
-
return Fail
|
|
215
228
|
}
|
|
216
|
-
}
|
|
217
229
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
230
|
+
while (true) {
|
|
231
|
+
const start = state.pos
|
|
232
|
+
failures.clear()
|
|
233
|
+
const q = tryOps(state, postfixSteps, true)
|
|
234
|
+
if (q === -2) return Fail
|
|
235
|
+
if (q >= 0) {
|
|
236
|
+
const op = postfixes[q]
|
|
237
|
+
if (op.precedence * 2 < minPower) {
|
|
238
|
+
state.rewind(start)
|
|
239
|
+
break
|
|
240
|
+
}
|
|
241
|
+
lhs = op.fold(lhs, matched)
|
|
242
|
+
continue
|
|
243
|
+
}
|
|
244
|
+
// The infix attempts are folded on top of the postfix ones
|
|
245
|
+
const i = tryOps(state, infixSteps, false)
|
|
246
|
+
if (i === -2) return Fail
|
|
247
|
+
if (i < 0) {
|
|
248
|
+
failures.hint(state)
|
|
249
|
+
break
|
|
250
|
+
}
|
|
251
|
+
const op = infixes[i]
|
|
252
|
+
const leftPower = op.precedence * 2
|
|
253
|
+
if (leftPower < minPower) {
|
|
226
254
|
state.rewind(start)
|
|
227
255
|
break
|
|
228
256
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
const i = tryOps(state, infixes, false)
|
|
234
|
-
if (i === -2) return Fail
|
|
235
|
-
if (i < 0) {
|
|
236
|
-
failures.hint(state)
|
|
237
|
-
break
|
|
257
|
+
const opOut = matched
|
|
258
|
+
const rhs = expr(state, op.associativity === "left" ? leftPower + 1 : leftPower)
|
|
259
|
+
if (rhs === Fail) return Fail
|
|
260
|
+
lhs = op.fold(lhs, opOut, rhs)
|
|
238
261
|
}
|
|
239
|
-
|
|
240
|
-
const leftPower = op.precedence * 2
|
|
241
|
-
if (leftPower < minPower) {
|
|
242
|
-
state.rewind(start)
|
|
243
|
-
break
|
|
244
|
-
}
|
|
245
|
-
const opOut = matched
|
|
246
|
-
const rhs = expr(state, op.associativity === "left" ? leftPower + 1 : leftPower)
|
|
247
|
-
if (rhs === Fail) return Fail
|
|
248
|
-
lhs = op.fold(lhs, opOut, rhs)
|
|
262
|
+
return lhs
|
|
249
263
|
}
|
|
250
|
-
return
|
|
264
|
+
return (state: State<any>) => expr(state, 0)
|
|
251
265
|
}
|
|
252
|
-
|
|
253
|
-
return makeParser((state: State<any>) => expr(state, 0), "expression")
|
|
266
|
+
return combinator(parsers, build, "expression")
|
|
254
267
|
}
|
package/src/Input.ts
CHANGED
|
@@ -19,6 +19,7 @@ import * as Context from "effect/Context"
|
|
|
19
19
|
import type { Encoding } from "./Encoding.js"
|
|
20
20
|
import type { ErrorKind } from "./ParseError.js"
|
|
21
21
|
import type { Builder as SyntaxBuilder } from "./internal/syntax.js"
|
|
22
|
+
import type { AsciiRun } from "./internal/text.js"
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
25
|
* The kinds of input a parser can consume.
|
|
@@ -56,6 +57,17 @@ export type BinaryInput = ArrayBufferLike | ArrayBufferView
|
|
|
56
57
|
*/
|
|
57
58
|
export type Source<I> = I | ([I] extends [Uint8Array] ? BinaryInput : never)
|
|
58
59
|
|
|
60
|
+
/**
|
|
61
|
+
* The overloads of {@link fromSource}.
|
|
62
|
+
*
|
|
63
|
+
* @since 0.5.0
|
|
64
|
+
* @category constructors
|
|
65
|
+
*/
|
|
66
|
+
export interface FromSourceFn {
|
|
67
|
+
(source: BinaryInput): Uint8Array
|
|
68
|
+
<I extends Input>(source: Source<I>): I
|
|
69
|
+
}
|
|
70
|
+
|
|
59
71
|
/**
|
|
60
72
|
* Converts a {@link Source} to the input parsers read: binary data becomes a
|
|
61
73
|
* `Uint8Array` view of the same memory, everything else is returned as is.
|
|
@@ -70,10 +82,7 @@ export type Source<I> = I | ([I] extends [Uint8Array] ? BinaryInput : never)
|
|
|
70
82
|
* @since 0.1.0
|
|
71
83
|
* @category constructors
|
|
72
84
|
*/
|
|
73
|
-
export const fromSource: {
|
|
74
|
-
(source: BinaryInput): Uint8Array
|
|
75
|
-
<I extends Input>(source: Source<I>): I
|
|
76
|
-
} = (source: Input | BinaryInput): any => {
|
|
85
|
+
export const fromSource: FromSourceFn = (source: Input | BinaryInput): any => {
|
|
77
86
|
if (Predicate.isString(source) || source instanceof Uint8Array || Array.isArray(source)) return source
|
|
78
87
|
if (ArrayBuffer.isView(source)) return new Uint8Array(source.buffer, source.byteOffset, source.byteLength)
|
|
79
88
|
// An explicit length fixes the view's size even for a resizable buffer
|
|
@@ -267,6 +276,18 @@ export class State<I extends Input = Input> {
|
|
|
267
276
|
this.deepestIsLatest = false
|
|
268
277
|
this.deepestFailure = failure
|
|
269
278
|
}
|
|
279
|
+
/**
|
|
280
|
+
* Whether compiled code is running on this state. A compiled run that fails
|
|
281
|
+
* is thrown away (the interpreter runs again to build the error) and one
|
|
282
|
+
* that succeeds records no diagnostics, so interpreted steps called from it
|
|
283
|
+
* (an opaque `Parser.make`, a leaf near the end of a partial input) record
|
|
284
|
+
* a `Backtrack` or `Cut` failure's mode only, without building its
|
|
285
|
+
* {@link Failure} (#82). `Incomplete` failures are still built: a compiled
|
|
286
|
+
* run's `Incomplete` is reported as it stands.
|
|
287
|
+
*
|
|
288
|
+
* @internal
|
|
289
|
+
*/
|
|
290
|
+
compiledRun = false
|
|
270
291
|
/** When defined, `Parser.resumeAfter`/`retryAfter` record failures here. */
|
|
271
292
|
recovered: Array<Failure> | undefined = undefined
|
|
272
293
|
/** When defined, `Parser.trace` reports events here. */
|
|
@@ -292,6 +313,33 @@ export class State<I extends Input = Input> {
|
|
|
292
313
|
* @internal
|
|
293
314
|
*/
|
|
294
315
|
syntax: SyntaxBuilder | undefined = undefined
|
|
316
|
+
/**
|
|
317
|
+
* Where the stop characters of compiled `takeTill`s next occur in this
|
|
318
|
+
* state's input, by search site; created on first use. It lives as long
|
|
319
|
+
* as the state. `reset` keeps it for string input (a string never
|
|
320
|
+
* changes, and the positions are absolute, so they hold wherever the next
|
|
321
|
+
* run starts) and drops it for bytes and arrays, which a caller may
|
|
322
|
+
* refill in place between runs.
|
|
323
|
+
*
|
|
324
|
+
* @internal
|
|
325
|
+
*/
|
|
326
|
+
searches: Map<object, Array<number>> | undefined = undefined
|
|
327
|
+
/**
|
|
328
|
+
* A run of ASCII bytes of this state's UTF-8 input, decoded once, from
|
|
329
|
+
* which `Text.utf8` slices the long strings it decodes (see
|
|
330
|
+
* `internal/text.ts`); `reset` clears it with the other per-run fields.
|
|
331
|
+
*
|
|
332
|
+
* @internal
|
|
333
|
+
*/
|
|
334
|
+
asciiRun: AsciiRun | undefined = undefined
|
|
335
|
+
/**
|
|
336
|
+
* A `DataView` of this state's byte input, for the `Binary` parsers;
|
|
337
|
+
* created on first use. It lives as long as the state, whose input never
|
|
338
|
+
* changes: one per read cost most of a number's time.
|
|
339
|
+
*
|
|
340
|
+
* @internal
|
|
341
|
+
*/
|
|
342
|
+
view: DataView | undefined = undefined
|
|
295
343
|
|
|
296
344
|
constructor(
|
|
297
345
|
source: I | Source<I>,
|
|
@@ -301,7 +349,7 @@ export class State<I extends Input = Input> {
|
|
|
301
349
|
readonly offset?: number | undefined
|
|
302
350
|
}
|
|
303
351
|
) {
|
|
304
|
-
const input = fromSource(source
|
|
352
|
+
const input = fromSource<I>(source)
|
|
305
353
|
this.input = input
|
|
306
354
|
this.end = input.length
|
|
307
355
|
this.pos = options?.offset ?? 0
|
|
@@ -349,7 +397,11 @@ export class State<I extends Input = Input> {
|
|
|
349
397
|
*
|
|
350
398
|
* Every per-run field is reset: the cursor and end, the latest failure,
|
|
351
399
|
* hints and furthest failure, recorded recoveries, the tracer and tracing
|
|
352
|
-
* depth, the recorded encoding and streaming checkpoints.
|
|
400
|
+
* depth, the recorded encoding and streaming checkpoints. For string
|
|
401
|
+
* input, what the state has learnt about the string (where the stop
|
|
402
|
+
* characters of `takeTill` and the literals of `takeUntilAny` occur) is
|
|
403
|
+
* kept, as a string cannot change; for bytes and arrays it is dropped, so
|
|
404
|
+
* a buffer refilled in place between runs is searched afresh. Like the
|
|
353
405
|
* constructor, `partial` defaults to `false` and `context` to the empty
|
|
354
406
|
* context, so pass them again on every reset.
|
|
355
407
|
*
|
|
@@ -388,11 +440,20 @@ export class State<I extends Input = Input> {
|
|
|
388
440
|
this.hintExpected = empty
|
|
389
441
|
this.deepestFailure = undefined
|
|
390
442
|
this.deepestIsLatest = false
|
|
443
|
+
this.compiledRun = false
|
|
391
444
|
this.recovered = undefined
|
|
392
445
|
this.tracer = undefined
|
|
393
446
|
this.depth = 0
|
|
394
447
|
this.encoding = undefined
|
|
395
448
|
this.resume = undefined
|
|
449
|
+
// For a string, `searches` is kept: it remembers where stop characters
|
|
450
|
+
// and literals occur by absolute position, and a string never changes,
|
|
451
|
+
// so it holds at any offset. A lexer that resets before each token
|
|
452
|
+
// otherwise searched to the end of the buffer again for every token
|
|
453
|
+
// (#126). Bytes and arrays can be refilled in place between runs (a
|
|
454
|
+
// `Uint8Array` source is viewed, not copied), so theirs is dropped.
|
|
455
|
+
if (this.inputKind !== 0) this.searches = undefined
|
|
456
|
+
this.asciiRun = undefined
|
|
396
457
|
return this
|
|
397
458
|
}
|
|
398
459
|
|
|
@@ -445,7 +506,14 @@ export class State<I extends Input = Input> {
|
|
|
445
506
|
* Extracts the slice `[start, end)` of the input.
|
|
446
507
|
*/
|
|
447
508
|
slice(start: number, end: number): Slice<I> {
|
|
448
|
-
|
|
509
|
+
// SAFETY: `inputKind` 1 is a `Uint8Array`, whose `subarray` returns a
|
|
510
|
+
// `Uint8Array` view (its `Slice`); 0 and 2 are a string and an array, whose
|
|
511
|
+
// `slice` returns a string and an array of the same elements (their
|
|
512
|
+
// `Slice`). `subarray` is only called for kind 1
|
|
513
|
+
const input = this.input as {
|
|
514
|
+
slice(start: number, end: number): Slice<I>
|
|
515
|
+
subarray(start: number, end: number): Slice<I>
|
|
516
|
+
} & I
|
|
449
517
|
switch (this.inputKind) {
|
|
450
518
|
case 0:
|
|
451
519
|
return input.slice(start, end)
|
|
@@ -460,6 +528,10 @@ export class State<I extends Input = Input> {
|
|
|
460
528
|
* Records a recoverable failure at the current offset and returns {@link Fail}.
|
|
461
529
|
*/
|
|
462
530
|
backtrack(kind: ErrorKind, expected: ReadonlyArray<string> = empty, offset: number = this.pos): Fail {
|
|
531
|
+
if (this.compiledRun) {
|
|
532
|
+
this.mode = "Backtrack"
|
|
533
|
+
return Fail
|
|
534
|
+
}
|
|
463
535
|
return this.failWith("Backtrack", this.makeFailure(offset, kind, expected))
|
|
464
536
|
}
|
|
465
537
|
|
|
@@ -467,6 +539,10 @@ export class State<I extends Input = Input> {
|
|
|
467
539
|
* Records an unrecoverable failure at the current offset and returns {@link Fail}.
|
|
468
540
|
*/
|
|
469
541
|
cut(kind: ErrorKind, expected: ReadonlyArray<string> = empty, offset: number = this.pos): Fail {
|
|
542
|
+
if (this.compiledRun) {
|
|
543
|
+
this.mode = "Cut"
|
|
544
|
+
return Fail
|
|
545
|
+
}
|
|
470
546
|
return this.failWith("Cut", this.makeFailure(offset, kind, expected))
|
|
471
547
|
}
|
|
472
548
|
|
|
@@ -598,14 +674,30 @@ export class State<I extends Input = Input> {
|
|
|
598
674
|
}
|
|
599
675
|
}
|
|
600
676
|
|
|
677
|
+
/**
|
|
678
|
+
* The last merges {@link mergeExpected} made, as `[a, b, merged]` triples. A
|
|
679
|
+
* parse merges the same few pairs of expectation arrays over and over (a
|
|
680
|
+
* whitespace skipper's hint with a repetition's, then with the failure of
|
|
681
|
+
* the token after them), and expectation arrays are never mutated, so a
|
|
682
|
+
* merge seen recently returns its earlier result, which in turn keeps the
|
|
683
|
+
* next merge's input the same array.
|
|
684
|
+
*/
|
|
685
|
+
const merges: Array<ReadonlyArray<string>> = /*#__PURE__*/ new Array(12).fill(empty)
|
|
686
|
+
let mergeSlot = 0
|
|
687
|
+
|
|
601
688
|
/** @internal */
|
|
602
689
|
export const mergeExpected = (a: ReadonlyArray<string>, b: ReadonlyArray<string>): ReadonlyArray<string> => {
|
|
603
690
|
if (Arr.isReadonlyArrayEmpty(a)) return b
|
|
604
691
|
if (Arr.isReadonlyArrayEmpty(b)) return a
|
|
692
|
+
for (let i = 0; i < 12; i += 3) if (merges[i] === a && merges[i + 1] === b) return merges[i + 2]
|
|
605
693
|
const out = a.slice()
|
|
606
694
|
for (const e of b) {
|
|
607
695
|
if (!out.includes(e)) out.push(e)
|
|
608
696
|
}
|
|
697
|
+
merges[mergeSlot] = a
|
|
698
|
+
merges[mergeSlot + 1] = b
|
|
699
|
+
merges[mergeSlot + 2] = out
|
|
700
|
+
mergeSlot = mergeSlot === 9 ? 0 : mergeSlot + 3
|
|
609
701
|
return out
|
|
610
702
|
}
|
|
611
703
|
|
package/src/Nom.ts
CHANGED
|
@@ -132,7 +132,9 @@ export const take = Token.take
|
|
|
132
132
|
*/
|
|
133
133
|
export const takeWhile = <I extends Input = string>(
|
|
134
134
|
predicate: (token: TokenOf<I>) => boolean
|
|
135
|
-
): Parser<Slice<NoInfer<I>>, never, never, NoInfer<I>> =>
|
|
135
|
+
): Parser<Slice<NoInfer<I>>, never, never, NoInfer<I>> =>
|
|
136
|
+
// SAFETY: a predicate over the input's tokens is a member of `TokenSet<I>` for each input kind
|
|
137
|
+
Token.takeWhile<I>(predicate as Token.TokenSet<I>)
|
|
136
138
|
|
|
137
139
|
/**
|
|
138
140
|
* @since 0.1.0
|
|
@@ -140,7 +142,9 @@ export const takeWhile = <I extends Input = string>(
|
|
|
140
142
|
*/
|
|
141
143
|
export const takeWhile1 = <I extends Input = string>(
|
|
142
144
|
predicate: (token: TokenOf<I>) => boolean
|
|
143
|
-
): Parser<Slice<NoInfer<I>>, never, never, NoInfer<I>> =>
|
|
145
|
+
): Parser<Slice<NoInfer<I>>, never, never, NoInfer<I>> =>
|
|
146
|
+
// SAFETY: a predicate over the input's tokens is a member of `TokenSet<I>` for each input kind
|
|
147
|
+
Token.takeWhile<I>(predicate as Token.TokenSet<I>, { min: 1 })
|
|
144
148
|
|
|
145
149
|
/**
|
|
146
150
|
* @since 0.1.0
|
|
@@ -150,7 +154,9 @@ export const takeWhileMN = <I extends Input = string>(
|
|
|
150
154
|
m: number,
|
|
151
155
|
n: number,
|
|
152
156
|
predicate: (token: TokenOf<I>) => boolean
|
|
153
|
-
): Parser<Slice<NoInfer<I>>, never, never, NoInfer<I>> =>
|
|
157
|
+
): Parser<Slice<NoInfer<I>>, never, never, NoInfer<I>> =>
|
|
158
|
+
// SAFETY: a predicate over the input's tokens is a member of `TokenSet<I>` for each input kind
|
|
159
|
+
Token.takeWhile<I>(predicate as Token.TokenSet<I>, { min: m, max: n })
|
|
154
160
|
|
|
155
161
|
/**
|
|
156
162
|
* @since 0.1.0
|
|
@@ -158,7 +164,9 @@ export const takeWhileMN = <I extends Input = string>(
|
|
|
158
164
|
*/
|
|
159
165
|
export const takeTill = <I extends Input = string>(
|
|
160
166
|
predicate: (token: TokenOf<I>) => boolean
|
|
161
|
-
): Parser<Slice<NoInfer<I>>, never, never, NoInfer<I>> =>
|
|
167
|
+
): Parser<Slice<NoInfer<I>>, never, never, NoInfer<I>> =>
|
|
168
|
+
// SAFETY: a predicate over the input's tokens is a member of `TokenSet<I>` for each input kind
|
|
169
|
+
Token.takeTill<I>(predicate as Token.TokenSet<I>)
|
|
162
170
|
|
|
163
171
|
/**
|
|
164
172
|
* @since 0.1.0
|
|
@@ -166,7 +174,9 @@ export const takeTill = <I extends Input = string>(
|
|
|
166
174
|
*/
|
|
167
175
|
export const takeTill1 = <I extends Input = string>(
|
|
168
176
|
predicate: (token: TokenOf<I>) => boolean
|
|
169
|
-
): Parser<Slice<NoInfer<I>>, never, never, NoInfer<I>> =>
|
|
177
|
+
): Parser<Slice<NoInfer<I>>, never, never, NoInfer<I>> =>
|
|
178
|
+
// SAFETY: a predicate over the input's tokens is a member of `TokenSet<I>` for each input kind
|
|
179
|
+
Token.takeTill<I>(predicate as Token.TokenSet<I>, { min: 1 })
|
|
170
180
|
|
|
171
181
|
/**
|
|
172
182
|
* @since 0.1.0
|