@thomasfosterau/effect-parse 0.1.0 → 0.3.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.
Files changed (161) hide show
  1. package/README.md +291 -231
  2. package/dist/Ascii.d.ts +32 -53
  3. package/dist/Ascii.d.ts.map +1 -1
  4. package/dist/Ascii.js +576 -571
  5. package/dist/Ascii.js.map +1 -1
  6. package/dist/Binary.d.ts +6 -29
  7. package/dist/Binary.d.ts.map +1 -1
  8. package/dist/Binary.js +129 -116
  9. package/dist/Binary.js.map +1 -1
  10. package/dist/Bits.d.ts +4 -2
  11. package/dist/Bits.d.ts.map +1 -1
  12. package/dist/Bits.js +121 -123
  13. package/dist/Bits.js.map +1 -1
  14. package/dist/Compiler.d.ts +39 -0
  15. package/dist/Compiler.d.ts.map +1 -0
  16. package/dist/Compiler.js +48 -0
  17. package/dist/Compiler.js.map +1 -0
  18. package/dist/Encoding.d.ts +29 -46
  19. package/dist/Encoding.d.ts.map +1 -1
  20. package/dist/Encoding.js +76 -75
  21. package/dist/Encoding.js.map +1 -1
  22. package/dist/Expression.d.ts +15 -49
  23. package/dist/Expression.d.ts.map +1 -1
  24. package/dist/Expression.js +151 -132
  25. package/dist/Expression.js.map +1 -1
  26. package/dist/Input.d.ts +251 -177
  27. package/dist/Input.d.ts.map +1 -1
  28. package/dist/Input.js +409 -299
  29. package/dist/Input.js.map +1 -1
  30. package/dist/Nom.d.ts +45 -85
  31. package/dist/Nom.d.ts.map +1 -1
  32. package/dist/Nom.js +423 -453
  33. package/dist/Nom.js.map +1 -1
  34. package/dist/ParseError.d.ts +54 -49
  35. package/dist/ParseError.d.ts.map +1 -1
  36. package/dist/ParseError.js +232 -275
  37. package/dist/ParseError.js.map +1 -1
  38. package/dist/Parser.d.ts +345 -313
  39. package/dist/Parser.d.ts.map +1 -1
  40. package/dist/Parser.js +2034 -1802
  41. package/dist/Parser.js.map +1 -1
  42. package/dist/ParserSchema.d.ts +206 -7
  43. package/dist/ParserSchema.d.ts.map +1 -1
  44. package/dist/ParserSchema.js +678 -49
  45. package/dist/ParserSchema.js.map +1 -1
  46. package/dist/ParserStream.d.ts +26 -20
  47. package/dist/ParserStream.d.ts.map +1 -1
  48. package/dist/ParserStream.js +267 -255
  49. package/dist/ParserStream.js.map +1 -1
  50. package/dist/Syntax.d.ts +401 -0
  51. package/dist/Syntax.d.ts.map +1 -0
  52. package/dist/Syntax.js +717 -0
  53. package/dist/Syntax.js.map +1 -0
  54. package/dist/Text.d.ts +98 -134
  55. package/dist/Text.d.ts.map +1 -1
  56. package/dist/Text.js +534 -496
  57. package/dist/Text.js.map +1 -1
  58. package/dist/Token.d.ts +60 -42
  59. package/dist/Token.d.ts.map +1 -1
  60. package/dist/Token.js +331 -265
  61. package/dist/Token.js.map +1 -1
  62. package/dist/index.d.ts +16 -17
  63. package/dist/index.js +16 -17
  64. package/dist/internal/compiler/closure.d.ts +14 -16
  65. package/dist/internal/compiler/closure.d.ts.map +1 -1
  66. package/dist/internal/compiler/closure.js +1458 -1637
  67. package/dist/internal/compiler/closure.js.map +1 -1
  68. package/dist/internal/compiler/entries.d.ts +11 -11
  69. package/dist/internal/compiler/entries.d.ts.map +1 -1
  70. package/dist/internal/compiler/entries.js +36 -26
  71. package/dist/internal/compiler/entries.js.map +1 -1
  72. package/dist/internal/compiler/first.d.ts +19 -19
  73. package/dist/internal/compiler/first.d.ts.map +1 -1
  74. package/dist/internal/compiler/first.js +251 -227
  75. package/dist/internal/compiler/first.js.map +1 -1
  76. package/dist/internal/compiler/jit.d.ts +6 -17
  77. package/dist/internal/compiler/jit.d.ts.map +1 -1
  78. package/dist/internal/compiler/jit.js +860 -988
  79. package/dist/internal/compiler/jit.js.map +1 -1
  80. package/dist/internal/compiler/registry.d.ts +20 -21
  81. package/dist/internal/compiler/registry.d.ts.map +1 -1
  82. package/dist/internal/compiler/registry.js +21 -41
  83. package/dist/internal/compiler/registry.js.map +1 -1
  84. package/dist/internal/compiler/sets.d.ts +9 -13
  85. package/dist/internal/compiler/sets.d.ts.map +1 -1
  86. package/dist/internal/compiler/sets.js +76 -73
  87. package/dist/internal/compiler/sets.js.map +1 -1
  88. package/dist/internal/entries.d.ts +3 -3
  89. package/dist/internal/entries.d.ts.map +1 -1
  90. package/dist/internal/entries.js +38 -30
  91. package/dist/internal/entries.js.map +1 -1
  92. package/dist/internal/node.d.ts +177 -173
  93. package/dist/internal/node.d.ts.map +1 -1
  94. package/dist/internal/node.js +77 -66
  95. package/dist/internal/node.js.map +1 -1
  96. package/dist/internal/numbers.d.ts +7 -8
  97. package/dist/internal/numbers.d.ts.map +1 -1
  98. package/dist/internal/numbers.js +246 -276
  99. package/dist/internal/numbers.js.map +1 -1
  100. package/dist/internal/resume.d.ts +6 -24
  101. package/dist/internal/resume.d.ts.map +1 -1
  102. package/dist/internal/resume.js +30 -29
  103. package/dist/internal/resume.js.map +1 -1
  104. package/dist/internal/run.d.ts +15 -0
  105. package/dist/internal/run.d.ts.map +1 -0
  106. package/dist/internal/run.js +36 -0
  107. package/dist/internal/run.js.map +1 -0
  108. package/dist/internal/stack.d.ts +16 -0
  109. package/dist/internal/stack.d.ts.map +1 -0
  110. package/dist/internal/stack.js +36 -0
  111. package/dist/internal/stack.js.map +1 -0
  112. package/dist/internal/syntax.d.ts +147 -0
  113. package/dist/internal/syntax.d.ts.map +1 -0
  114. package/dist/internal/syntax.js +309 -0
  115. package/dist/internal/syntax.js.map +1 -0
  116. package/dist/internal/text.d.ts +10 -2
  117. package/dist/internal/text.d.ts.map +1 -1
  118. package/dist/internal/text.js +295 -345
  119. package/dist/internal/text.js.map +1 -1
  120. package/dist/internal/ties.d.ts +30 -0
  121. package/dist/internal/ties.d.ts.map +1 -0
  122. package/dist/internal/ties.js +67 -0
  123. package/dist/internal/ties.js.map +1 -0
  124. package/dist/internal/tokens.d.ts +25 -30
  125. package/dist/internal/tokens.d.ts.map +1 -1
  126. package/dist/internal/tokens.js +369 -363
  127. package/dist/internal/tokens.js.map +1 -1
  128. package/package.json +42 -28
  129. package/src/Ascii.ts +278 -187
  130. package/src/Binary.ts +150 -44
  131. package/src/Bits.ts +6 -3
  132. package/src/Compiler.ts +98 -0
  133. package/src/Expression.ts +24 -16
  134. package/src/Input.ts +210 -35
  135. package/src/Nom.ts +4 -3
  136. package/src/ParseError.ts +49 -52
  137. package/src/Parser.ts +1199 -799
  138. package/src/ParserSchema.ts +982 -23
  139. package/src/ParserStream.ts +104 -80
  140. package/src/Syntax.ts +900 -0
  141. package/src/Text.ts +272 -187
  142. package/src/Token.ts +180 -87
  143. package/src/index.ts +2 -0
  144. package/src/internal/compiler/closure.ts +121 -88
  145. package/src/internal/compiler/entries.ts +21 -8
  146. package/src/internal/compiler/first.ts +17 -15
  147. package/src/internal/compiler/jit.ts +360 -157
  148. package/src/internal/compiler/registry.ts +12 -33
  149. package/src/internal/compiler/sets.ts +17 -11
  150. package/src/internal/entries.ts +4 -2
  151. package/src/internal/node.ts +51 -37
  152. package/src/internal/numbers.ts +22 -20
  153. package/src/internal/resume.ts +8 -4
  154. package/src/internal/run.ts +41 -0
  155. package/src/internal/stack.ts +41 -0
  156. package/src/internal/syntax.ts +426 -0
  157. package/src/internal/text.ts +32 -25
  158. package/src/internal/ties.ts +83 -0
  159. package/src/internal/tokens.ts +96 -51
  160. package/dist/index.d.ts.map +0 -1
  161. package/dist/index.js.map +0 -1
@@ -1,15 +1,13 @@
1
1
  /**
2
- * The registry of compiled parsers, keyed by parser (see `Parser.compile`).
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, entry.backend, resumable)
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
- typeof u === "object" && u !== null && (u as any)._tag === "TokenRange"
25
+ Predicate.isObjectOrArray(u) && (u as any)._tag === "TokenRange"
22
26
 
23
- const toCode = (u: string | number): number => typeof u === "number" ? u : u.codePointAt(0)!
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
- export const rangesOf = (set: unknown): Array<[number, number]> | undefined => {
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 (typeof u === "string") {
33
+ if (Predicate.isString(u)) {
30
34
  for (const c of u) {
31
- const cp = c.codePointAt(0)!
35
+ const cp = firstCodePoint(c)
32
36
  out.push([cp, cp])
33
37
  }
34
38
  return true
35
39
  }
36
- if (typeof u === "number") {
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 (typeof u === "function") {
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 out) {
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 = typeof enc.encode("") === "string" ? 0 : 1)
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>()
@@ -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 => typeof key === "string" && !hasOwn.call(objectPrototype, key)
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 && (typeof key !== "object" || key === null) && typeof key !== "function") {
41
+ if (deferred === undefined && !Predicate.isObjectOrArray(key) && !Predicate.isFunction(key)) {
40
42
  define(o, key, value)
41
43
  return undefined
42
44
  }
@@ -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
- readonly _tag: "Escaped"
57
- readonly normal: P
58
- /** The control character, as a code point (or a byte / unit for `Ascii`). */
59
- readonly control: number
60
- readonly escape: P
61
- /** `takeEscaped`: return the raw slice instead of the unescaped text. */
62
- readonly raw: boolean
63
- readonly encoding: Enc
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
- readonly _tag: "Seq"
92
- readonly parsers: ReadonlyArray<P>
93
- /** Which outputs are kept: all (a tuple), one index, or a pair of indices. */
94
- readonly output: "tuple" | number | readonly [number, number]
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
- readonly _tag: "Dispatch"
102
- readonly selector: P
103
- /** A record of cases, when `dispatch` was given one. */
104
- readonly cases: Readonly<Record<PropertyKey, P>> | undefined
105
- readonly otherwise: P | undefined
106
- readonly choose: (key: any) => P | undefined
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
- readonly _tag: "Fold"
112
- readonly self: P
113
- readonly init: () => unknown
114
- readonly f: (acc: any, a: any) => unknown
115
- readonly name: string
116
- } & Bounds)
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
- readonly _tag: "SeparatedFold"
121
- readonly self: P
122
- readonly separator: P
123
- readonly op: (l: any, s: any, r: any) => unknown
124
- readonly right: boolean
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 = (range: number | { readonly min?: number | undefined; readonly max?: number | undefined } | undefined): 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 (typeof range === "number") return { min: range, max: range }
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
@@ -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
- const toText = (s: Input): string =>
10
- typeof s === "string" ? s : s instanceof Uint8Array ? latin1.text(s, 0, s.length) : s.join("")
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 (typeof s === "string") {
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 = ((ah * bh - p) + ah * bl + al * bh) + al * bl
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]! : 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 = ((wh - productHi) - productLo + wl) / p
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]! : -1
216
+ let c = i < end ? b[i] : -1
215
217
  if (c === 45 || c === 43) {
216
- c = i + 1 < end ? b[i + 1]! : -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]! : -1
240
+ c = ++i < end ? b[i] : -1
239
241
  } while (isDigit(c))
240
- if (c === 46) c = ++i < end ? b[i]! : -1
242
+ if (c === 46) c = ++i < end ? b[i] : -1
241
243
  } else {
242
- c = ++i < end ? b[i]! : -1
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]! : -1
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]! : -1
269
+ c = j < end ? b[j] : -1
268
270
  const negative = c === 45
269
- if (negative || c === 43) c = ++j < end ? b[j]! : -1
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]! : -1
276
+ c = ++j < end ? b[j] : -1
275
277
  } while (isDigit(c))
276
278
  e10 += negative ? -e : e
277
279
  i = j
@@ -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!.get(key)
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(readonly value: unknown, readonly pos: number) {}
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,41 @@
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 * as Predicate from "effect/Predicate"
11
+ import type { Fail, State } from "../Input.js"
12
+
13
+ /** The message of a `TooDeep` failure. */
14
+ const tooDeepMessage = "input is nested too deeply: parsing exhausted the call stack"
15
+
16
+ /**
17
+ * Whether `e` is the engine's call-stack overflow: a `RangeError` in V8
18
+ * and JavaScriptCore, an `InternalError` ("too much recursion") in
19
+ * SpiderMonkey.
20
+ */
21
+ export const isStackOverflow = (e: unknown): boolean =>
22
+ (e instanceof RangeError && /call stack/i.test(e.message)) ||
23
+ (Predicate.hasProperty(e, "name") &&
24
+ Predicate.hasProperty(e, "message") &&
25
+ e.name === "InternalError" &&
26
+ Predicate.isString(e.message) &&
27
+ /recursion/i.test(e.message))
28
+
29
+ /**
30
+ * Runs `step`, turning a call-stack overflow into an unrecoverable
31
+ * `TooDeep` failure at the offset parsing had reached. Other exceptions
32
+ * propagate.
33
+ */
34
+ export const run = <S extends State<any>, A>(step: (state: S) => A, state: S): A | Fail => {
35
+ try {
36
+ return step(state)
37
+ } catch (e) {
38
+ if (!isStackOverflow(e)) throw e
39
+ return state.failMessage("Cut", "TooDeep", tooDeepMessage)
40
+ }
41
+ }