@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.
Files changed (157) hide show
  1. package/CHANGELOG.md +202 -0
  2. package/README.md +56 -14
  3. package/dist/Ascii.d.ts +11 -4
  4. package/dist/Ascii.d.ts.map +1 -1
  5. package/dist/Ascii.js +12 -4
  6. package/dist/Ascii.js.map +1 -1
  7. package/dist/Binary.d.ts +5 -2
  8. package/dist/Binary.d.ts.map +1 -1
  9. package/dist/Binary.js +83 -37
  10. package/dist/Binary.js.map +1 -1
  11. package/dist/Bits.d.ts.map +1 -1
  12. package/dist/Bits.js +11 -4
  13. package/dist/Bits.js.map +1 -1
  14. package/dist/Compiler.d.ts +74 -2
  15. package/dist/Compiler.d.ts.map +1 -1
  16. package/dist/Compiler.js +78 -1
  17. package/dist/Compiler.js.map +1 -1
  18. package/dist/Expression.d.ts.map +1 -1
  19. package/dist/Expression.js +86 -74
  20. package/dist/Expression.js.map +1 -1
  21. package/dist/Input.d.ts +56 -5
  22. package/dist/Input.d.ts.map +1 -1
  23. package/dist/Input.js +70 -1
  24. package/dist/Input.js.map +1 -1
  25. package/dist/Nom.d.ts +5 -15
  26. package/dist/Nom.d.ts.map +1 -1
  27. package/dist/Nom.js.map +1 -1
  28. package/dist/ParseError.d.ts +69 -11
  29. package/dist/ParseError.d.ts.map +1 -1
  30. package/dist/ParseError.js +129 -28
  31. package/dist/ParseError.js.map +1 -1
  32. package/dist/Parser.d.ts +375 -29
  33. package/dist/Parser.d.ts.map +1 -1
  34. package/dist/Parser.js +394 -37
  35. package/dist/Parser.js.map +1 -1
  36. package/dist/ParserSchema.d.ts.map +1 -1
  37. package/dist/ParserSchema.js +2 -3
  38. package/dist/ParserSchema.js.map +1 -1
  39. package/dist/ParserStream.d.ts +122 -14
  40. package/dist/ParserStream.d.ts.map +1 -1
  41. package/dist/ParserStream.js +293 -62
  42. package/dist/ParserStream.js.map +1 -1
  43. package/dist/Syntax.d.ts +23 -5
  44. package/dist/Syntax.d.ts.map +1 -1
  45. package/dist/Syntax.js +1 -1
  46. package/dist/Syntax.js.map +1 -1
  47. package/dist/Text.d.ts.map +1 -1
  48. package/dist/Text.js +21 -15
  49. package/dist/Text.js.map +1 -1
  50. package/dist/Token.d.ts +65 -11
  51. package/dist/Token.d.ts.map +1 -1
  52. package/dist/Token.js +73 -26
  53. package/dist/Token.js.map +1 -1
  54. package/dist/internal/compiler/closure.d.ts +1 -1
  55. package/dist/internal/compiler/closure.d.ts.map +1 -1
  56. package/dist/internal/compiler/closure.js +263 -113
  57. package/dist/internal/compiler/closure.js.map +1 -1
  58. package/dist/internal/compiler/entries.d.ts +3 -1
  59. package/dist/internal/compiler/entries.d.ts.map +1 -1
  60. package/dist/internal/compiler/entries.js +3 -2
  61. package/dist/internal/compiler/entries.js.map +1 -1
  62. package/dist/internal/compiler/first.d.ts.map +1 -1
  63. package/dist/internal/compiler/first.js +9 -2
  64. package/dist/internal/compiler/first.js.map +1 -1
  65. package/dist/internal/compiler/graph.d.ts +39 -0
  66. package/dist/internal/compiler/graph.d.ts.map +1 -0
  67. package/dist/internal/compiler/graph.js +78 -0
  68. package/dist/internal/compiler/graph.js.map +1 -0
  69. package/dist/internal/compiler/jit.d.ts +5 -0
  70. package/dist/internal/compiler/jit.d.ts.map +1 -1
  71. package/dist/internal/compiler/jit.js +208 -106
  72. package/dist/internal/compiler/jit.js.map +1 -1
  73. package/dist/internal/compiler/registry.d.ts +11 -3
  74. package/dist/internal/compiler/registry.d.ts.map +1 -1
  75. package/dist/internal/compiler/registry.js.map +1 -1
  76. package/dist/internal/compiler/search.d.ts +32 -0
  77. package/dist/internal/compiler/search.d.ts.map +1 -0
  78. package/dist/internal/compiler/search.js +68 -0
  79. package/dist/internal/compiler/search.js.map +1 -0
  80. package/dist/internal/compiler/sets.d.ts.map +1 -1
  81. package/dist/internal/compiler/sets.js +1 -1
  82. package/dist/internal/compiler/sets.js.map +1 -1
  83. package/dist/internal/compiler/wasm.d.ts +8 -0
  84. package/dist/internal/compiler/wasm.d.ts.map +1 -0
  85. package/dist/internal/compiler/wasm.js +2840 -0
  86. package/dist/internal/compiler/wasm.js.map +1 -0
  87. package/dist/internal/compiler/wasmAsm.d.ts +199 -0
  88. package/dist/internal/compiler/wasmAsm.d.ts.map +1 -0
  89. package/dist/internal/compiler/wasmAsm.js +427 -0
  90. package/dist/internal/compiler/wasmAsm.js.map +1 -0
  91. package/dist/internal/entries.d.ts +3 -1
  92. package/dist/internal/entries.d.ts.map +1 -1
  93. package/dist/internal/entries.js.map +1 -1
  94. package/dist/internal/node.d.ts +77 -3
  95. package/dist/internal/node.d.ts.map +1 -1
  96. package/dist/internal/node.js +54 -10
  97. package/dist/internal/node.js.map +1 -1
  98. package/dist/internal/numbers.d.ts.map +1 -1
  99. package/dist/internal/numbers.js.map +1 -1
  100. package/dist/internal/run.d.ts +14 -0
  101. package/dist/internal/run.d.ts.map +1 -1
  102. package/dist/internal/run.js +53 -11
  103. package/dist/internal/run.js.map +1 -1
  104. package/dist/internal/stack.d.ts +14 -0
  105. package/dist/internal/stack.d.ts.map +1 -1
  106. package/dist/internal/stack.js +24 -2
  107. package/dist/internal/stack.js.map +1 -1
  108. package/dist/internal/syntax.d.ts +4 -4
  109. package/dist/internal/syntax.d.ts.map +1 -1
  110. package/dist/internal/syntax.js +18 -10
  111. package/dist/internal/syntax.js.map +1 -1
  112. package/dist/internal/text.d.ts +32 -0
  113. package/dist/internal/text.d.ts.map +1 -1
  114. package/dist/internal/text.js +68 -14
  115. package/dist/internal/text.js.map +1 -1
  116. package/dist/internal/tokens.d.ts +20 -0
  117. package/dist/internal/tokens.d.ts.map +1 -1
  118. package/dist/internal/tokens.js +72 -39
  119. package/dist/internal/tokens.js.map +1 -1
  120. package/dist/internal/tuple.d.ts +6 -0
  121. package/dist/internal/tuple.d.ts.map +1 -0
  122. package/dist/internal/tuple.js +170 -0
  123. package/dist/internal/tuple.js.map +1 -0
  124. package/package.json +18 -10
  125. package/src/Ascii.ts +47 -12
  126. package/src/Binary.ts +174 -73
  127. package/src/Bits.ts +16 -6
  128. package/src/Compiler.ts +101 -10
  129. package/src/Expression.ts +94 -81
  130. package/src/Input.ts +99 -7
  131. package/src/Nom.ts +15 -5
  132. package/src/ParseError.ts +209 -52
  133. package/src/Parser.ts +684 -143
  134. package/src/ParserSchema.ts +1 -2
  135. package/src/ParserStream.ts +361 -68
  136. package/src/Syntax.ts +60 -36
  137. package/src/Text.ts +32 -27
  138. package/src/Token.ts +152 -34
  139. package/src/internal/compiler/closure.ts +371 -153
  140. package/src/internal/compiler/entries.ts +4 -2
  141. package/src/internal/compiler/first.ts +16 -0
  142. package/src/internal/compiler/graph.ts +96 -0
  143. package/src/internal/compiler/jit.ts +319 -169
  144. package/src/internal/compiler/registry.ts +10 -2
  145. package/src/internal/compiler/search.ts +68 -0
  146. package/src/internal/compiler/sets.ts +7 -3
  147. package/src/internal/compiler/wasm.ts +3276 -0
  148. package/src/internal/compiler/wasmAsm.ts +484 -0
  149. package/src/internal/entries.ts +8 -2
  150. package/src/internal/node.ts +103 -7
  151. package/src/internal/numbers.ts +3 -1
  152. package/src/internal/run.ts +73 -12
  153. package/src/internal/stack.ts +25 -1
  154. package/src/internal/syntax.ts +39 -18
  155. package/src/internal/text.ts +97 -9
  156. package/src/internal/tokens.ts +106 -14
  157. package/src/internal/tuple.ts +141 -0
@@ -24,11 +24,13 @@ export const pairOf = (p: ParserLike): EntryPair | undefined => {
24
24
  return { parsers: node.parsers, key: output[0], value: output[1] }
25
25
  }
26
26
 
27
- const isDiagnostic = (
27
+ /** Whether a node only changes diagnostics: its parser parses as its child does. */
28
+ export const isDiagnostic = (
28
29
  node: Node
29
- ): node is NodeOf<"Context" | "Expected" | "MapError" | "OrFail" | "Trace" | "Syntax" | "RetryAfter"> =>
30
+ ): node is NodeOf<"Context" | "Expected" | "Quiet" | "MapError" | "OrFail" | "Trace" | "Syntax" | "RetryAfter"> =>
30
31
  Predicate.isTagged(node, "Context") ||
31
32
  Predicate.isTagged(node, "Expected") ||
33
+ Predicate.isTagged(node, "Quiet") ||
32
34
  Predicate.isTagged(node, "MapError") ||
33
35
  Predicate.isTagged(node, "OrFail") ||
34
36
  Predicate.isTagged(node, "Trace") ||
@@ -92,6 +92,7 @@ export class FirstSets {
92
92
  }
93
93
 
94
94
  private compute(node: Node): First {
95
+ // SAFETY: `compute` is only called from `of`, which returns before it when `kind` is 2
95
96
  const kind = this.kind as 0 | 1
96
97
  switch (node._tag) {
97
98
  case "Succeed":
@@ -111,6 +112,7 @@ export class FirstSets {
111
112
  if (node.encoding !== undefined) {
112
113
  if (kindOfEncoding(node.encoding) !== kind) return unknown
113
114
  if (node.caseless) return isEmptyUnits(lit) ? unknown : anyUnit
115
+ // SAFETY: a `Literal` with an encoding is a `Text` literal, which records the string it encodes
114
116
  const encoded = node.encoding.encode(lit as string)
115
117
  if (isEmptyUnits(encoded)) return emptyOptional
116
118
  return ofUnits([Predicate.isString(encoded) ? encoded.charCodeAt(0) : encoded[0]])
@@ -141,13 +143,18 @@ export class FirstSets {
141
143
  case "Tap":
142
144
  case "TryMap":
143
145
  case "FilterMap":
146
+ case "MapOrFail":
144
147
  case "Filter":
145
148
  case "Taken":
146
149
  case "WithTaken":
147
150
  case "Span":
148
151
  case "WithSpan":
152
+ case "MapSpan":
149
153
  case "Context":
150
154
  case "Expected":
155
+ case "Quiet":
156
+ // A fast path accepts what its grammar accepts (`Parser.withFastPath`)
157
+ case "FastPath":
151
158
  case "MapError":
152
159
  case "OrFail":
153
160
  case "Peek":
@@ -166,6 +173,12 @@ export class FirstSets {
166
173
  const f = this.of(node.self)
167
174
  return node.from === "Backtrack" ? (f.mode === Unknown ? f : unknown) : f
168
175
  }
176
+ // `not(p)` consumes nothing. Where `p`'s first set rules a unit out, `p`
177
+ // backtracks there, so `not(p)` succeeds without consuming: Optional
178
+ // over `p`'s units. On a unit in that set it may also fail, Cut
179
+ // included, which Optional leaves open. So `seq(not(p), q)` starts
180
+ // with `p`'s units and `q`'s, and an `alt` can skip it on any other (#84)
181
+ case "Not":
169
182
  case "Opt":
170
183
  case "OrElseSucceed": {
171
184
  const f = this.of(node.self)
@@ -173,6 +186,7 @@ export class FirstSets {
173
186
  }
174
187
  case "Seq":
175
188
  case "Struct":
189
+ case "SeqWith":
176
190
  return this.sequence(node.parsers)
177
191
  case "ZipWith":
178
192
  return this.sequence([node.self, node.that])
@@ -198,6 +212,8 @@ export class FirstSets {
198
212
  }
199
213
  case "Suspend":
200
214
  return this.of(node.resolve())
215
+ case "Fixed":
216
+ return kind !== 1 ? unknown : node.size === 0 ? emptyOptional : anyUnit
201
217
  default:
202
218
  return unknown
203
219
  }
@@ -0,0 +1,96 @@
1
+ /**
2
+ * The shape of a grammar, as the code-generating compilers (the JIT and the
3
+ * WebAssembly compiler) see it: which parsers are leaves, which are shared
4
+ * and which are small enough to inline everywhere.
5
+ */
6
+ import * as Predicate from "effect/Predicate"
7
+ import type { Node, ParserLike } from "../node.js"
8
+ import { children, nodeOf } from "../node.js"
9
+
10
+ /** Whether a node is a token or a constant, always generated inline. */
11
+ export const isLeaf = (node: Node): boolean =>
12
+ Predicate.isTagged(node, "Succeed") ||
13
+ Predicate.isTagged(node, "Fail") ||
14
+ Predicate.isTagged(node, "Eof") ||
15
+ Predicate.isTagged(node, "Location") ||
16
+ Predicate.isTagged(node, "RestLen") ||
17
+ Predicate.isTagged(node, "Any") ||
18
+ Predicate.isTagged(node, "Literal") ||
19
+ Predicate.isTagged(node, "OneOf") ||
20
+ Predicate.isTagged(node, "TakeWhile")
21
+
22
+ /**
23
+ * How many times each parser reachable from `root` is used: one for every
24
+ * place a parent mentions it. A parser used twice is generated once, as a
25
+ * rule the two places call (unless it is small, see {@link isSmall}).
26
+ *
27
+ * Uses are counted, not distinct parents: `seq(p, p)` uses `p` twice. When
28
+ * only parents were counted, such a `p` was inlined at both places, and a
29
+ * grammar that does it at every level (`p = seq(p, p)`, twenty times over)
30
+ * compiled to 2^20 copies: 160 MB of source from twenty lines (#122).
31
+ */
32
+ export const countRefs = (root: ParserLike): WeakMap<ParserLike, number> => {
33
+ const refs = new WeakMap<ParserLike, number>()
34
+ const seen = new WeakSet<ParserLike>()
35
+ const stack = [root]
36
+ for (let p = stack.pop(); p !== undefined; p = stack.pop()) {
37
+ if (seen.has(p)) continue
38
+ seen.add(p)
39
+ for (const c of children(nodeOf(p))) {
40
+ refs.set(c, (refs.get(c) ?? 0) + 1)
41
+ stack.push(c)
42
+ }
43
+ }
44
+ return refs
45
+ }
46
+
47
+ /**
48
+ * The most parsers a code-generating compiler inlines for one root. Sharing
49
+ * is what keeps generated code proportional to the grammar; a grammar that
50
+ * is itself enormous (a tree of distinct parsers built by a loop, a derived
51
+ * schema) would still take minutes and gigabytes to generate, so past this
52
+ * many the compiler gives up and the next one down takes over (the JIT for
53
+ * WebAssembly, the closure compiler for the JIT), which costs a closure per
54
+ * parser instead of source text. The largest grammars in this repository
55
+ * and the libraries built on it inline a few thousand.
56
+ */
57
+ export const MaxInlined = 100_000
58
+
59
+ /** Counts a parser a compiler is about to inline, failing the compilation past {@link MaxInlined}. */
60
+ export const countInlined = (inlined: number): number => {
61
+ if (inlined >= MaxInlined) {
62
+ throw new RangeError(
63
+ `effect-parse: the grammar is too large to generate code for (over ${MaxInlined} parsers inlined)`
64
+ )
65
+ }
66
+ return inlined + 1
67
+ }
68
+
69
+ /** Whether a parser is small enough to inline wherever it is used. */
70
+ export const isSmall = (p: ParserLike): boolean => {
71
+ let budget = 6
72
+ const visit = (q: ParserLike): boolean => {
73
+ const node = nodeOf(q)
74
+ if (
75
+ --budget < 0 ||
76
+ Predicate.isTagged(node, "Suspend") ||
77
+ Predicate.isTagged(node, "Escaped") ||
78
+ Predicate.isTagged(node, "Dispatch")
79
+ )
80
+ return false
81
+ return children(node).every(visit)
82
+ }
83
+ return visit(p)
84
+ }
85
+
86
+ /** The node a `dispatch` selects on, and whether it is looked at with `peek`. */
87
+ export const selectorOf = (selector: ParserLike): { readonly node: Node; readonly peek: boolean } => {
88
+ const node = nodeOf(selector)
89
+ return Predicate.isTagged(node, "Peek") ? { node: nodeOf(node.self), peek: true } : { node, peek: false }
90
+ }
91
+
92
+ /** ASCII lower case. */
93
+ export const lowerAscii = (c: number): number => (c >= 65 && c <= 90 ? c + 32 : c)
94
+
95
+ /** Whether a code is an ASCII letter. */
96
+ export const isLetter = (c: number): boolean => (c >= 65 && c <= 90) || (c >= 97 && c <= 122)