@thomasfosterau/effect-parse 0.0.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +247 -230
- package/dist/Ascii.d.ts +32 -53
- package/dist/Ascii.d.ts.map +1 -1
- package/dist/Ascii.js +576 -571
- package/dist/Ascii.js.map +1 -1
- package/dist/Binary.d.ts +6 -29
- package/dist/Binary.d.ts.map +1 -1
- package/dist/Binary.js +129 -116
- package/dist/Binary.js.map +1 -1
- package/dist/Bits.d.ts +4 -2
- package/dist/Bits.d.ts.map +1 -1
- package/dist/Bits.js +121 -123
- package/dist/Bits.js.map +1 -1
- package/dist/Compiler.d.ts +39 -0
- package/dist/Compiler.d.ts.map +1 -0
- package/dist/Compiler.js +48 -0
- package/dist/Compiler.js.map +1 -0
- package/dist/Encoding.d.ts +29 -46
- package/dist/Encoding.d.ts.map +1 -1
- package/dist/Encoding.js +76 -75
- package/dist/Encoding.js.map +1 -1
- package/dist/Expression.d.ts +15 -49
- package/dist/Expression.d.ts.map +1 -1
- package/dist/Expression.js +143 -131
- package/dist/Expression.js.map +1 -1
- package/dist/Input.d.ts +206 -177
- package/dist/Input.d.ts.map +1 -1
- package/dist/Input.js +317 -299
- package/dist/Input.js.map +1 -1
- package/dist/Nom.d.ts +44 -84
- package/dist/Nom.d.ts.map +1 -1
- package/dist/Nom.js +423 -453
- package/dist/Nom.js.map +1 -1
- package/dist/ParseError.d.ts +54 -49
- package/dist/ParseError.d.ts.map +1 -1
- package/dist/ParseError.js +233 -275
- package/dist/ParseError.js.map +1 -1
- package/dist/Parser.d.ts +345 -313
- package/dist/Parser.d.ts.map +1 -1
- package/dist/Parser.js +2010 -1802
- package/dist/Parser.js.map +1 -1
- package/dist/ParserSchema.d.ts +8 -7
- package/dist/ParserSchema.d.ts.map +1 -1
- package/dist/ParserSchema.js +49 -49
- package/dist/ParserSchema.js.map +1 -1
- package/dist/ParserStream.d.ts +26 -20
- package/dist/ParserStream.d.ts.map +1 -1
- package/dist/ParserStream.js +267 -255
- package/dist/ParserStream.js.map +1 -1
- package/dist/Syntax.d.ts +401 -0
- package/dist/Syntax.d.ts.map +1 -0
- package/dist/Syntax.js +717 -0
- package/dist/Syntax.js.map +1 -0
- package/dist/Text.d.ts +98 -134
- package/dist/Text.d.ts.map +1 -1
- package/dist/Text.js +534 -496
- package/dist/Text.js.map +1 -1
- package/dist/Token.d.ts +60 -42
- package/dist/Token.d.ts.map +1 -1
- package/dist/Token.js +331 -265
- package/dist/Token.js.map +1 -1
- package/dist/index.d.ts +16 -17
- package/dist/index.js +16 -17
- package/dist/internal/compiler/closure.d.ts +14 -16
- package/dist/internal/compiler/closure.d.ts.map +1 -1
- package/dist/internal/compiler/closure.js +1458 -1637
- package/dist/internal/compiler/closure.js.map +1 -1
- package/dist/internal/compiler/entries.d.ts +11 -11
- package/dist/internal/compiler/entries.d.ts.map +1 -1
- package/dist/internal/compiler/entries.js +36 -26
- package/dist/internal/compiler/entries.js.map +1 -1
- package/dist/internal/compiler/first.d.ts +19 -19
- package/dist/internal/compiler/first.d.ts.map +1 -1
- package/dist/internal/compiler/first.js +251 -227
- package/dist/internal/compiler/first.js.map +1 -1
- package/dist/internal/compiler/jit.d.ts +6 -17
- package/dist/internal/compiler/jit.d.ts.map +1 -1
- package/dist/internal/compiler/jit.js +860 -988
- package/dist/internal/compiler/jit.js.map +1 -1
- package/dist/internal/compiler/registry.d.ts +20 -21
- package/dist/internal/compiler/registry.d.ts.map +1 -1
- package/dist/internal/compiler/registry.js +21 -41
- package/dist/internal/compiler/registry.js.map +1 -1
- package/dist/internal/compiler/sets.d.ts +9 -13
- package/dist/internal/compiler/sets.d.ts.map +1 -1
- package/dist/internal/compiler/sets.js +76 -73
- package/dist/internal/compiler/sets.js.map +1 -1
- package/dist/internal/entries.d.ts +3 -3
- package/dist/internal/entries.d.ts.map +1 -1
- package/dist/internal/entries.js +38 -30
- package/dist/internal/entries.js.map +1 -1
- package/dist/internal/node.d.ts +177 -173
- package/dist/internal/node.d.ts.map +1 -1
- package/dist/internal/node.js +77 -66
- package/dist/internal/node.js.map +1 -1
- package/dist/internal/numbers.d.ts +7 -8
- package/dist/internal/numbers.d.ts.map +1 -1
- package/dist/internal/numbers.js +246 -276
- package/dist/internal/numbers.js.map +1 -1
- package/dist/internal/resume.d.ts +6 -24
- package/dist/internal/resume.d.ts.map +1 -1
- package/dist/internal/resume.js +30 -29
- package/dist/internal/resume.js.map +1 -1
- package/dist/internal/run.d.ts +15 -0
- package/dist/internal/run.d.ts.map +1 -0
- package/dist/internal/run.js +36 -0
- package/dist/internal/run.js.map +1 -0
- package/dist/internal/stack.d.ts +16 -0
- package/dist/internal/stack.d.ts.map +1 -0
- package/dist/internal/stack.js +26 -0
- package/dist/internal/stack.js.map +1 -0
- package/dist/internal/syntax.d.ts +146 -0
- package/dist/internal/syntax.d.ts.map +1 -0
- package/dist/internal/syntax.js +305 -0
- package/dist/internal/syntax.js.map +1 -0
- package/dist/internal/text.d.ts +10 -2
- package/dist/internal/text.d.ts.map +1 -1
- package/dist/internal/text.js +295 -345
- package/dist/internal/text.js.map +1 -1
- package/dist/internal/tokens.d.ts +25 -30
- package/dist/internal/tokens.d.ts.map +1 -1
- package/dist/internal/tokens.js +369 -363
- package/dist/internal/tokens.js.map +1 -1
- package/package.json +42 -28
- package/src/Ascii.ts +278 -187
- package/src/Binary.ts +150 -44
- package/src/Bits.ts +6 -3
- package/src/Compiler.ts +98 -0
- package/src/Expression.ts +10 -5
- package/src/Input.ts +106 -28
- package/src/Nom.ts +4 -3
- package/src/ParseError.ts +48 -51
- package/src/Parser.ts +1163 -795
- package/src/ParserStream.ts +104 -80
- package/src/Syntax.ts +900 -0
- package/src/Text.ts +272 -187
- package/src/Token.ts +180 -87
- package/src/index.ts +2 -0
- package/src/internal/compiler/closure.ts +121 -88
- package/src/internal/compiler/entries.ts +21 -8
- package/src/internal/compiler/first.ts +17 -15
- package/src/internal/compiler/jit.ts +360 -157
- package/src/internal/compiler/registry.ts +12 -33
- package/src/internal/compiler/sets.ts +17 -11
- package/src/internal/entries.ts +4 -2
- package/src/internal/node.ts +51 -37
- package/src/internal/numbers.ts +22 -20
- package/src/internal/resume.ts +8 -4
- package/src/internal/run.ts +41 -0
- package/src/internal/stack.ts +36 -0
- package/src/internal/syntax.ts +421 -0
- package/src/internal/text.ts +32 -25
- package/src/internal/tokens.ts +96 -51
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The registry of compiled parsers, keyed by parser (see `
|
|
3
|
-
* Compiled steps are built lazily, once per input kind.
|
|
2
|
+
* The registry of compiled parsers, keyed by parser (see the `Compiler`
|
|
3
|
+
* module). Compiled steps are built lazily, once per input kind.
|
|
4
|
+
*
|
|
5
|
+
* Nothing here imports a compiler: a parser is registered with the backend
|
|
6
|
+
* that compiles it, so a program bundles only the backends it uses.
|
|
4
7
|
*/
|
|
5
8
|
import type { State } from "../../Input.js"
|
|
6
9
|
import type { ParserLike } from "../node.js"
|
|
7
10
|
import type { Fallback, Step } from "./closure.js"
|
|
8
|
-
import { compileClosure } from "./closure.js"
|
|
9
|
-
import { compileJit, jitSupported } from "./jit.js"
|
|
10
|
-
|
|
11
|
-
/** Which compiler to use. */
|
|
12
|
-
export type Backend = "auto" | "closure"
|
|
13
11
|
|
|
14
12
|
/** What was compiled for a parser and an input kind. */
|
|
15
13
|
export interface Compiled {
|
|
@@ -26,42 +24,23 @@ export interface Compiled {
|
|
|
26
24
|
readonly source?: string
|
|
27
25
|
}
|
|
28
26
|
|
|
27
|
+
/** A compiler: builds the compiled form of a parser for an input kind. */
|
|
28
|
+
export interface Backend {
|
|
29
|
+
readonly build: (p: ParserLike, kind: 0 | 1 | 2, resumable: boolean) => Compiled
|
|
30
|
+
}
|
|
31
|
+
|
|
29
32
|
interface Entry {
|
|
30
33
|
readonly backend: Backend
|
|
31
34
|
readonly byKind: Array<Compiled | undefined>
|
|
32
35
|
}
|
|
33
36
|
|
|
34
|
-
const registry = new WeakMap<ParserLike, Entry>()
|
|
37
|
+
const registry = /*#__PURE__*/ new WeakMap<ParserLike, Entry>()
|
|
35
38
|
|
|
36
39
|
export const register = (p: ParserLike, backend: Backend): void => {
|
|
37
40
|
const entry = registry.get(p)
|
|
38
41
|
if (entry === undefined || entry.backend !== backend) registry.set(p, { backend, byKind: [] })
|
|
39
42
|
}
|
|
40
43
|
|
|
41
|
-
export const isRegistered = (p: ParserLike): boolean => registry.has(p)
|
|
42
|
-
|
|
43
|
-
const build = (p: ParserLike, kind: 0 | 1 | 2, backend: Backend, resumable: boolean): Compiled => {
|
|
44
|
-
if (backend === "auto" && jitSupported()) {
|
|
45
|
-
try {
|
|
46
|
-
const { fallbacks, source, step } = compileJit(p, kind, resumable)
|
|
47
|
-
return { backend: "jit", step, fallbacks, error: undefined, source }
|
|
48
|
-
} catch (error) {
|
|
49
|
-
// Fall back to the closure compiler, remembering why
|
|
50
|
-
return { ...buildClosure(p, kind, resumable), jitError: error }
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
return buildClosure(p, kind, resumable)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const buildClosure = (p: ParserLike, kind: 0 | 1 | 2, resumable: boolean): Compiled => {
|
|
57
|
-
try {
|
|
58
|
-
const { fallbacks, step } = compileClosure(p, kind, resumable)
|
|
59
|
-
return { backend: "closure", step, fallbacks, error: undefined }
|
|
60
|
-
} catch (error) {
|
|
61
|
-
return { backend: undefined, step: undefined, fallbacks: [], error }
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
44
|
/**
|
|
66
45
|
* The compiled form of a registered parser for an input kind, compiling it
|
|
67
46
|
* if needed. `resumable` selects the build used by streaming parses, which
|
|
@@ -71,7 +50,7 @@ export const compiled = (p: ParserLike, kind: 0 | 1 | 2, resumable = false): Com
|
|
|
71
50
|
const entry = registry.get(p)
|
|
72
51
|
if (entry === undefined) return undefined
|
|
73
52
|
const index = resumable ? kind + 3 : kind
|
|
74
|
-
return entry.byKind[index] ??= build(p, kind,
|
|
53
|
+
return (entry.byKind[index] ??= entry.backend.build(p, kind, resumable))
|
|
75
54
|
}
|
|
76
55
|
|
|
77
56
|
/** The compiled step for running `p` on `state`, if it is registered. */
|
|
@@ -2,7 +2,11 @@
|
|
|
2
2
|
* Token sets for compiled parsers: membership tables for small units, the
|
|
3
3
|
* exact test for the rest, and the code point ranges when they are known.
|
|
4
4
|
*/
|
|
5
|
+
import * as Arr from "effect/Array"
|
|
6
|
+
import * as Order from "effect/Order"
|
|
7
|
+
import * as Predicate from "effect/Predicate"
|
|
5
8
|
import type { Encoding } from "../../Encoding.js"
|
|
9
|
+
import { firstCodePoint } from "../text.js"
|
|
6
10
|
import { compile, compileAny, PredicateRanges } from "../tokens.js"
|
|
7
11
|
|
|
8
12
|
/** Size of the membership table: every byte, and the Latin-1 code points. */
|
|
@@ -18,22 +22,22 @@ export interface CharSet {
|
|
|
18
22
|
}
|
|
19
23
|
|
|
20
24
|
const isRange = (u: unknown): u is { readonly from: string | number; readonly to: string | number } =>
|
|
21
|
-
|
|
25
|
+
Predicate.isObjectOrArray(u) && (u as any)._tag === "TokenRange"
|
|
22
26
|
|
|
23
|
-
const toCode = (u: string | number): number =>
|
|
27
|
+
const toCode = (u: string | number): number => (Predicate.isNumber(u) ? u : firstCodePoint(u))
|
|
24
28
|
|
|
25
29
|
/** The code point ranges of a token set, or `undefined` if it has an unknown predicate. */
|
|
26
|
-
|
|
30
|
+
const rangesOf = (set: unknown): Array<[number, number]> | undefined => {
|
|
27
31
|
const out: Array<[number, number]> = []
|
|
28
32
|
const add = (u: unknown): boolean => {
|
|
29
|
-
if (
|
|
33
|
+
if (Predicate.isString(u)) {
|
|
30
34
|
for (const c of u) {
|
|
31
|
-
const cp = c
|
|
35
|
+
const cp = firstCodePoint(c)
|
|
32
36
|
out.push([cp, cp])
|
|
33
37
|
}
|
|
34
38
|
return true
|
|
35
39
|
}
|
|
36
|
-
if (
|
|
40
|
+
if (Predicate.isNumber(u)) {
|
|
37
41
|
out.push([u, u])
|
|
38
42
|
return true
|
|
39
43
|
}
|
|
@@ -45,7 +49,7 @@ export const rangesOf = (set: unknown): Array<[number, number]> | undefined => {
|
|
|
45
49
|
out.push([toCode(u.from), toCode(u.to)])
|
|
46
50
|
return true
|
|
47
51
|
}
|
|
48
|
-
if (
|
|
52
|
+
if (Predicate.isFunction(u)) {
|
|
49
53
|
const r = (u as any)[PredicateRanges] as ReadonlyArray<readonly [number, number]> | undefined
|
|
50
54
|
if (r === undefined) return false
|
|
51
55
|
for (const [a, b] of r) out.push([a, b])
|
|
@@ -55,9 +59,11 @@ export const rangesOf = (set: unknown): Array<[number, number]> | undefined => {
|
|
|
55
59
|
return false
|
|
56
60
|
}
|
|
57
61
|
if (!add(set)) return undefined
|
|
58
|
-
out.sort((a, b) => a[0] - b[0])
|
|
59
62
|
const merged: Array<[number, number]> = []
|
|
60
|
-
for (const [a, b] of
|
|
63
|
+
for (const [a, b] of Arr.sort(
|
|
64
|
+
out,
|
|
65
|
+
Order.mapInput(Order.Number, (range: readonly [number, number]) => range[0])
|
|
66
|
+
)) {
|
|
61
67
|
if (b < a) continue
|
|
62
68
|
const last = merged[merged.length - 1]
|
|
63
69
|
if (last !== undefined && a <= last[1] + 1) last[1] = Math.max(last[1], b)
|
|
@@ -93,8 +99,8 @@ export const reachesAbove = (s: CharSet, limit: number): boolean =>
|
|
|
93
99
|
/** The input kind a `Text` encoding reads: 0 for strings, 1 for bytes. */
|
|
94
100
|
export const kindOfEncoding = (enc: Encoding<any>): 0 | 1 => {
|
|
95
101
|
let k = kinds.get(enc)
|
|
96
|
-
if (k === undefined) kinds.set(enc, k =
|
|
102
|
+
if (k === undefined) kinds.set(enc, (k = Predicate.isString(enc.encode("")) ? 0 : 1))
|
|
97
103
|
return k
|
|
98
104
|
}
|
|
99
105
|
|
|
100
|
-
const kinds = new WeakMap<Encoding<any>, 0 | 1>()
|
|
106
|
+
const kinds = /*#__PURE__*/ new WeakMap<Encoding<any>, 0 | 1>()
|
package/src/internal/entries.ts
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
* repetition of pairs with `Object.fromEntries` (see `Parser.fromEntries`).
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import * as Predicate from "effect/Predicate"
|
|
8
|
+
|
|
7
9
|
/** The built-in `Object.fromEntries`, which compiled parsers recognize. */
|
|
8
10
|
export const fromEntries: <K extends PropertyKey, V>(entries: Iterable<readonly [K, V]>) => { [P in K]: V } =
|
|
9
11
|
Object.fromEntries as any
|
|
@@ -17,7 +19,7 @@ const hasOwn = objectPrototype.hasOwnProperty
|
|
|
17
19
|
* property): `key` is a string that `Object.prototype` has no property for,
|
|
18
20
|
* so there is no setter (`__proto__`) or read-only property to run into.
|
|
19
21
|
*/
|
|
20
|
-
export const isPlainKey = (key: unknown): key is string =>
|
|
22
|
+
export const isPlainKey = (key: unknown): key is string => Predicate.isString(key) && !hasOwn.call(objectPrototype, key)
|
|
21
23
|
|
|
22
24
|
const define = (o: object, key: unknown, value: unknown): void => {
|
|
23
25
|
Object.defineProperty(o, key as PropertyKey, { value, writable: true, enumerable: true, configurable: true })
|
|
@@ -36,7 +38,7 @@ export const addEntry = (
|
|
|
36
38
|
key: unknown,
|
|
37
39
|
value: unknown
|
|
38
40
|
): Array<unknown> | undefined => {
|
|
39
|
-
if (deferred === undefined && (
|
|
41
|
+
if (deferred === undefined && !Predicate.isObjectOrArray(key) && !Predicate.isFunction(key)) {
|
|
40
42
|
define(o, key, value)
|
|
41
43
|
return undefined
|
|
42
44
|
}
|
package/src/internal/node.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* child's own step is always at hand for fallback, and recursion through
|
|
7
7
|
* `Suspend` is just a reference.
|
|
8
8
|
*/
|
|
9
|
+
import * as Predicate from "effect/Predicate"
|
|
9
10
|
import type { Encoding } from "../Encoding.js"
|
|
10
11
|
import type { ErrMode, Input, State } from "../Input.js"
|
|
11
12
|
|
|
@@ -53,15 +54,15 @@ export type Node =
|
|
|
53
54
|
| { readonly _tag: "Identifier"; readonly encoding: Enc }
|
|
54
55
|
| { readonly _tag: "Bom"; readonly encoding: Enc }
|
|
55
56
|
| {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
57
|
+
readonly _tag: "Escaped"
|
|
58
|
+
readonly normal: P
|
|
59
|
+
/** The control character, as a code point (or a byte / unit for `Ascii`). */
|
|
60
|
+
readonly control: number
|
|
61
|
+
readonly escape: P
|
|
62
|
+
/** `takeEscaped`: return the raw slice instead of the unescaped text. */
|
|
63
|
+
readonly raw: boolean
|
|
64
|
+
readonly encoding: Enc
|
|
65
|
+
}
|
|
65
66
|
// Mapping
|
|
66
67
|
| { readonly _tag: "Map"; readonly self: P; readonly f: (a: any) => unknown }
|
|
67
68
|
| { readonly _tag: "As"; readonly self: P; readonly value: unknown }
|
|
@@ -88,62 +89,74 @@ export type Node =
|
|
|
88
89
|
| { readonly _tag: "Not"; readonly self: P }
|
|
89
90
|
// Sequencing
|
|
90
91
|
| {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
92
|
+
readonly _tag: "Seq"
|
|
93
|
+
readonly parsers: ReadonlyArray<P>
|
|
94
|
+
/** Which outputs are kept: all (a tuple), one index, or a pair of indices. */
|
|
95
|
+
readonly output: "tuple" | number | readonly [number, number]
|
|
96
|
+
}
|
|
96
97
|
| { readonly _tag: "Struct"; readonly keys: ReadonlyArray<string>; readonly parsers: ReadonlyArray<P> }
|
|
97
98
|
| { readonly _tag: "ZipWith"; readonly self: P; readonly that: P; readonly f: (a: any, b: any) => unknown }
|
|
98
99
|
// Choice
|
|
99
100
|
| { readonly _tag: "Alt"; readonly parsers: ReadonlyArray<P> }
|
|
100
101
|
| {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
102
|
+
readonly _tag: "Dispatch"
|
|
103
|
+
readonly selector: P
|
|
104
|
+
/** A record of cases, when `dispatch` was given one. */
|
|
105
|
+
readonly cases: Readonly<Record<PropertyKey, P>> | undefined
|
|
106
|
+
readonly otherwise: P | undefined
|
|
107
|
+
readonly choose: (key: any) => P | undefined
|
|
108
|
+
}
|
|
108
109
|
// Repetition
|
|
109
110
|
| ({ readonly _tag: "Repeat"; readonly self: P } & Bounds)
|
|
110
111
|
| ({
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
112
|
+
readonly _tag: "Fold"
|
|
113
|
+
readonly self: P
|
|
114
|
+
readonly init: () => unknown
|
|
115
|
+
readonly f: (acc: any, a: any) => unknown
|
|
116
|
+
/** Stops the repetition once false of the accumulator (after `min` elements). */
|
|
117
|
+
readonly while: ((acc: any) => boolean) | undefined
|
|
118
|
+
readonly name: string
|
|
119
|
+
} & Bounds)
|
|
117
120
|
| ({ readonly _tag: "RepeatTill"; readonly self: P; readonly end: P } & Bounds)
|
|
118
121
|
| ({ readonly _tag: "Separated"; readonly self: P; readonly separator: P } & Bounds)
|
|
119
122
|
| {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
123
|
+
readonly _tag: "SeparatedFold"
|
|
124
|
+
readonly self: P
|
|
125
|
+
readonly separator: P
|
|
126
|
+
readonly op: (l: any, s: any, r: any) => unknown
|
|
127
|
+
readonly right: boolean
|
|
128
|
+
}
|
|
126
129
|
// Services, recovery, debugging
|
|
127
130
|
| { readonly _tag: "Service" }
|
|
128
131
|
| { readonly _tag: "Provide"; readonly self: P }
|
|
129
132
|
| { readonly _tag: "ResumeAfter"; readonly self: P; readonly recover: P }
|
|
130
133
|
| { readonly _tag: "RetryAfter"; readonly self: P; readonly recover: P }
|
|
131
134
|
| { readonly _tag: "Trace"; readonly self: P; readonly name: string }
|
|
135
|
+
/** `Syntax.node`, `Syntax.token` and `Syntax.field`: identities outside `Syntax` parses. */
|
|
136
|
+
| {
|
|
137
|
+
readonly _tag: "Syntax"
|
|
138
|
+
readonly self: P
|
|
139
|
+
/** The node kind, or `undefined` for a field. */
|
|
140
|
+
readonly kind: string | undefined
|
|
141
|
+
readonly named: boolean
|
|
142
|
+
}
|
|
132
143
|
|
|
133
144
|
export type Tag = Node["_tag"]
|
|
134
145
|
|
|
135
146
|
export type NodeOf<T extends Tag> = Extract<Node, { readonly _tag: T }>
|
|
136
147
|
|
|
137
148
|
/** Where a parser's description is stored. */
|
|
138
|
-
export const NodeId: unique symbol = Symbol.for("effect-parse/Parser/node")
|
|
149
|
+
export const NodeId: unique symbol = /*#__PURE__*/ Symbol.for("effect-parse/Parser/node")
|
|
139
150
|
|
|
140
151
|
/** The description of a parser: `Opaque` when it has none. */
|
|
141
152
|
export const nodeOf = (p: ParserLike): Node => (p as any)[NodeId] ?? { _tag: "Opaque", step: p.step }
|
|
142
153
|
|
|
143
154
|
/** Normalizes a range, as `repeat`, `takeWhile`, ... do. */
|
|
144
|
-
export const bounds = (
|
|
155
|
+
export const bounds = (
|
|
156
|
+
range: number | { readonly min?: number | undefined; readonly max?: number | undefined } | undefined
|
|
157
|
+
): Bounds => {
|
|
145
158
|
if (range === undefined) return { min: 0, max: Infinity }
|
|
146
|
-
if (
|
|
159
|
+
if (Predicate.isNumber(range)) return { min: range, max: range }
|
|
147
160
|
return { min: range.min ?? 0, max: range.max ?? Infinity }
|
|
148
161
|
}
|
|
149
162
|
|
|
@@ -176,6 +189,7 @@ export const children = (node: Node): ReadonlyArray<P> => {
|
|
|
176
189
|
case "Fold":
|
|
177
190
|
case "Provide":
|
|
178
191
|
case "Trace":
|
|
192
|
+
case "Syntax":
|
|
179
193
|
return [node.self]
|
|
180
194
|
case "ResumeAfter":
|
|
181
195
|
case "RetryAfter":
|
|
@@ -189,7 +203,7 @@ export const children = (node: Node): ReadonlyArray<P> => {
|
|
|
189
203
|
case "Dispatch": {
|
|
190
204
|
const out: Array<P> = [node.selector]
|
|
191
205
|
if (node.cases !== undefined) {
|
|
192
|
-
for (const key of Reflect.ownKeys(node.cases)) out.push(node.cases[key as any]
|
|
206
|
+
for (const key of Reflect.ownKeys(node.cases)) out.push(node.cases[key as any])
|
|
193
207
|
}
|
|
194
208
|
if (node.otherwise !== undefined) out.push(node.otherwise)
|
|
195
209
|
return out
|
package/src/internal/numbers.ts
CHANGED
|
@@ -2,16 +2,18 @@
|
|
|
2
2
|
* Number conversions shared by the `Ascii` and `Text` parsers and the
|
|
3
3
|
* compilers.
|
|
4
4
|
*/
|
|
5
|
+
import * as Predicate from "effect/Predicate"
|
|
5
6
|
import type { Input } from "../Input.js"
|
|
6
7
|
import { Fail } from "../Input.js"
|
|
7
8
|
import { latin1 } from "./text.js"
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
/** The text of an input slice: as is for a string, Latin-1 for bytes, joined for tokens. */
|
|
11
|
+
export const toText = (s: Input): string =>
|
|
12
|
+
Predicate.isString(s) ? s : s instanceof Uint8Array ? latin1.text(s, 0, s.length) : s.join("")
|
|
11
13
|
|
|
12
14
|
/** The value of the text matched by `Ascii.float` and `Text.float`. */
|
|
13
15
|
export const toFloat = (s: Input): number => {
|
|
14
|
-
if (
|
|
16
|
+
if (Predicate.isString(s)) {
|
|
15
17
|
// Plain decimal notation (as opposed to `inf` or `nan`) ends in a digit or "."
|
|
16
18
|
const last = s.charCodeAt(s.length - 1)
|
|
17
19
|
if ((last >= 48 && last <= 57) || last === 46) return Number(s)
|
|
@@ -29,7 +31,7 @@ export const toFloat = (s: Input): number => {
|
|
|
29
31
|
* compilers replace them by {@link scanFloatString} and
|
|
30
32
|
* {@link scanFloatBytes} when the text is in decimal notation.
|
|
31
33
|
*/
|
|
32
|
-
export const floatTexts = new WeakSet<object>()
|
|
34
|
+
export const floatTexts = /*#__PURE__*/ new WeakSet<object>()
|
|
33
35
|
|
|
34
36
|
/** Adds a parser to {@link floatTexts}. */
|
|
35
37
|
export const floatText = <P extends object>(p: P): P => {
|
|
@@ -38,7 +40,7 @@ export const floatText = <P extends object>(p: P): P => {
|
|
|
38
40
|
}
|
|
39
41
|
|
|
40
42
|
/** The powers of ten that doubles represent exactly. */
|
|
41
|
-
const exact = Array.from({ length: 23 }, (_, i) => Number(`1e${i}`))
|
|
43
|
+
const exact = /*#__PURE__*/ Array.from({ length: 23 }, (_, i) => Number(`1e${i}`))
|
|
42
44
|
|
|
43
45
|
/**
|
|
44
46
|
* Where the latest scan ended, when it succeeded, and the furthest offset it
|
|
@@ -63,7 +65,7 @@ const twoProduct = (a: number, b: number): void => {
|
|
|
63
65
|
const bh = t - (t - b)
|
|
64
66
|
const bl = b - bh
|
|
65
67
|
productHi = p
|
|
66
|
-
productLo =
|
|
68
|
+
productLo = ah * bh - p + ah * bl + al * bh + al * bl
|
|
67
69
|
}
|
|
68
70
|
|
|
69
71
|
/** 2^-96: a bound on the relative error of {@link decimal}'s 19-digit path. */
|
|
@@ -87,25 +89,25 @@ const decimal = (negative: boolean, high: number, low: number, lowDigits: number
|
|
|
87
89
|
let v: number
|
|
88
90
|
if (lowDigits === 0) {
|
|
89
91
|
if (e10 > 22 || e10 < -22) return undefined
|
|
90
|
-
v = e10 === 0 ? high : e10 > 0 ? high * exact[e10]
|
|
92
|
+
v = e10 === 0 ? high : e10 > 0 ? high * exact[e10] : high / exact[-e10]
|
|
91
93
|
} else {
|
|
92
94
|
if (e10 > 22 || e10 < -22) return undefined
|
|
93
95
|
// The digits: high * 10^lowDigits + low = wh + wl, exactly
|
|
94
|
-
twoProduct(high, exact[lowDigits]
|
|
96
|
+
twoProduct(high, exact[lowDigits])
|
|
95
97
|
const wh = productHi + low
|
|
96
98
|
const wl = productLo + (low - (wh - productHi))
|
|
97
99
|
let xh: number
|
|
98
100
|
let xl: number
|
|
99
101
|
if (e10 >= 0) {
|
|
100
|
-
const p = exact[e10]
|
|
102
|
+
const p = exact[e10]
|
|
101
103
|
twoProduct(wh, p)
|
|
102
104
|
xh = productHi
|
|
103
105
|
xl = productLo + wl * p
|
|
104
106
|
} else {
|
|
105
|
-
const p = exact[-e10]
|
|
107
|
+
const p = exact[-e10]
|
|
106
108
|
xh = wh / p
|
|
107
109
|
twoProduct(xh, p)
|
|
108
|
-
xl = (
|
|
110
|
+
xl = (wh - productHi - productLo + wl) / p
|
|
109
111
|
}
|
|
110
112
|
v = xh + xl
|
|
111
113
|
const error = (xh < 0 ? -xh : xh) * Error19
|
|
@@ -211,9 +213,9 @@ export const scanFloatString = (s: string, i: number, end: number): number | Fai
|
|
|
211
213
|
/** {@link scanFloatString} on bytes, or the units of an ASCII compatible encoding. */
|
|
212
214
|
export const scanFloatBytes = (b: Uint8Array, i: number, end: number): number | Fail | undefined => {
|
|
213
215
|
const start = i
|
|
214
|
-
let c = i < end ? b[i]
|
|
216
|
+
let c = i < end ? b[i] : -1
|
|
215
217
|
if (c === 45 || c === 43) {
|
|
216
|
-
c = i + 1 < end ? b[i + 1]
|
|
218
|
+
c = i + 1 < end ? b[i + 1] : -1
|
|
217
219
|
if (!isDigit(c) && c !== 46) return undefined
|
|
218
220
|
i++
|
|
219
221
|
} else if (!isDigit(c) && c !== 46) {
|
|
@@ -235,11 +237,11 @@ export const scanFloatBytes = (b: Uint8Array, i: number, end: number): number |
|
|
|
235
237
|
else e10++
|
|
236
238
|
digits++
|
|
237
239
|
}
|
|
238
|
-
c = ++i < end ? b[i]
|
|
240
|
+
c = ++i < end ? b[i] : -1
|
|
239
241
|
} while (isDigit(c))
|
|
240
|
-
if (c === 46) c = ++i < end ? b[i]
|
|
242
|
+
if (c === 46) c = ++i < end ? b[i] : -1
|
|
241
243
|
} else {
|
|
242
|
-
c = ++i < end ? b[i]
|
|
244
|
+
c = ++i < end ? b[i] : -1
|
|
243
245
|
if (!isDigit(c)) {
|
|
244
246
|
floatScan.read = i
|
|
245
247
|
return Fail
|
|
@@ -259,19 +261,19 @@ export const scanFloatBytes = (b: Uint8Array, i: number, end: number): number |
|
|
|
259
261
|
}
|
|
260
262
|
digits++
|
|
261
263
|
}
|
|
262
|
-
c = ++i < end ? b[i]
|
|
264
|
+
c = ++i < end ? b[i] : -1
|
|
263
265
|
}
|
|
264
266
|
let read = i
|
|
265
267
|
if (c === 101 || c === 69) {
|
|
266
268
|
let j = i + 1
|
|
267
|
-
c = j < end ? b[j]
|
|
269
|
+
c = j < end ? b[j] : -1
|
|
268
270
|
const negative = c === 45
|
|
269
|
-
if (negative || c === 43) c = ++j < end ? b[j]
|
|
271
|
+
if (negative || c === 43) c = ++j < end ? b[j] : -1
|
|
270
272
|
if (isDigit(c)) {
|
|
271
273
|
let e = 0
|
|
272
274
|
do {
|
|
273
275
|
if (e < 1e6) e = e * 10 + (c - 48)
|
|
274
|
-
c = ++j < end ? b[j]
|
|
276
|
+
c = ++j < end ? b[j] : -1
|
|
275
277
|
} while (isDigit(c))
|
|
276
278
|
e10 += negative ? -e : e
|
|
277
279
|
i = j
|
package/src/internal/resume.ts
CHANGED
|
@@ -25,7 +25,7 @@ export type Checkpoints = Map<object, Map<number, unknown>>
|
|
|
25
25
|
|
|
26
26
|
/** Takes (and removes) the checkpoint of `key` entered at `offset`, if any. */
|
|
27
27
|
export const take = (state: State<any>, key: object, offset: number): unknown => {
|
|
28
|
-
const byKey = state.resume
|
|
28
|
+
const byKey = state.resume?.get(key)
|
|
29
29
|
if (byKey === undefined) return undefined
|
|
30
30
|
const checkpoint = byKey.get(offset)
|
|
31
31
|
if (checkpoint !== undefined && !(checkpoint instanceof Done)) byKey.delete(offset)
|
|
@@ -34,7 +34,10 @@ export const take = (state: State<any>, key: object, offset: number): unknown =>
|
|
|
34
34
|
|
|
35
35
|
/** The result of a repetition that completed, and where it ended. */
|
|
36
36
|
export class Done {
|
|
37
|
-
constructor(
|
|
37
|
+
constructor(
|
|
38
|
+
readonly value: unknown,
|
|
39
|
+
readonly pos: number
|
|
40
|
+
) {}
|
|
38
41
|
}
|
|
39
42
|
|
|
40
43
|
/**
|
|
@@ -48,8 +51,9 @@ export const done = <A>(state: State<any>, key: object, entry: number, resumed:
|
|
|
48
51
|
|
|
49
52
|
/** Records the checkpoint of `key` entered at `offset`. */
|
|
50
53
|
export const save = (state: State<any>, key: object, offset: number, checkpoint: unknown): void => {
|
|
51
|
-
const resume = state.resume
|
|
54
|
+
const resume = state.resume
|
|
55
|
+
if (resume === undefined) return
|
|
52
56
|
let byKey = resume.get(key)
|
|
53
|
-
if (byKey === undefined) resume.set(key, byKey = new Map())
|
|
57
|
+
if (byKey === undefined) resume.set(key, (byKey = new Map()))
|
|
54
58
|
byKey.set(offset, checkpoint)
|
|
55
59
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Running a parser on a caller-owned `State`, compiled when possible.
|
|
3
|
+
*
|
|
4
|
+
* Shared by `Parser.run` and `ParserStream.parseMany`, which keep one
|
|
5
|
+
* state across many small runs.
|
|
6
|
+
*/
|
|
7
|
+
import { Fail, type State } from "../Input.js"
|
|
8
|
+
import * as Registry from "./compiler/registry.js"
|
|
9
|
+
import type { ParserLike } from "./node.js"
|
|
10
|
+
import * as Stack from "./stack.js"
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Runs `p` on `state` from `state.pos`: its compiled form when it has one
|
|
14
|
+
* and the state allows it, else its step. A compiled run that fails is
|
|
15
|
+
* thrown away and the step runs again from the same position, so a
|
|
16
|
+
* failure leaves the state exactly as the interpreter would; with
|
|
17
|
+
* `diagnostics` false it is not, and the state keeps the compiled run's
|
|
18
|
+
* `mode`, with the cursor back at the start. Partial states (and states
|
|
19
|
+
* with streaming checkpoints) use the resumable build.
|
|
20
|
+
*/
|
|
21
|
+
export const runOn = <A>(
|
|
22
|
+
p: ParserLike,
|
|
23
|
+
step: (state: State<any>) => A | Fail,
|
|
24
|
+
state: State<any>,
|
|
25
|
+
diagnostics: boolean = true
|
|
26
|
+
): A | Fail => {
|
|
27
|
+
if (state.tracer === undefined && state.recovered === undefined && state.syntax === undefined) {
|
|
28
|
+
const compiled = Registry.stepFor(p, state, state.partial || state.resume !== undefined)
|
|
29
|
+
if (compiled !== undefined) {
|
|
30
|
+
const start = state.pos
|
|
31
|
+
const a = Stack.run(compiled, state)
|
|
32
|
+
if (a !== Fail) {
|
|
33
|
+
// SAFETY: the compiled step returns what the parser's step returns
|
|
34
|
+
return a as A
|
|
35
|
+
}
|
|
36
|
+
state.pos = start
|
|
37
|
+
if (!diagnostics) return Fail
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return Stack.run(step, state)
|
|
41
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Call-stack overflows.
|
|
3
|
+
*
|
|
4
|
+
* The interpreter and the compiled backends recurse once (or more) per
|
|
5
|
+
* nesting level of a recursive grammar, so deeply nested input, such as
|
|
6
|
+
* thousands of `{`, can exhaust the call stack. The runners catch the
|
|
7
|
+
* engine's overflow error and report a `TooDeep` failure instead, keeping
|
|
8
|
+
* their `Result` contract on untrusted input.
|
|
9
|
+
*/
|
|
10
|
+
import type { Fail, State } from "../Input.js"
|
|
11
|
+
|
|
12
|
+
/** The message of a `TooDeep` failure. */
|
|
13
|
+
const tooDeepMessage = "input is nested too deeply: parsing exhausted the call stack"
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Whether `e` is the engine's call-stack overflow: a `RangeError` in V8
|
|
17
|
+
* and JavaScriptCore, an `InternalError` ("too much recursion") in
|
|
18
|
+
* SpiderMonkey.
|
|
19
|
+
*/
|
|
20
|
+
export const isStackOverflow = (e: unknown): boolean =>
|
|
21
|
+
(e instanceof RangeError && /call stack/i.test(e.message)) ||
|
|
22
|
+
(e instanceof Error && e.name === "InternalError" && /recursion/i.test(e.message))
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Runs `step`, turning a call-stack overflow into an unrecoverable
|
|
26
|
+
* `TooDeep` failure at the offset parsing had reached. Other exceptions
|
|
27
|
+
* propagate.
|
|
28
|
+
*/
|
|
29
|
+
export const run = <S extends State<any>, A>(step: (state: S) => A, state: S): A | Fail => {
|
|
30
|
+
try {
|
|
31
|
+
return step(state)
|
|
32
|
+
} catch (e) {
|
|
33
|
+
if (!isStackOverflow(e)) throw e
|
|
34
|
+
return state.failMessage("Cut", "TooDeep", tooDeepMessage)
|
|
35
|
+
}
|
|
36
|
+
}
|