@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
package/src/Syntax.ts ADDED
@@ -0,0 +1,900 @@
1
+ /**
2
+ * Concrete syntax trees and incremental parsing, in the spirit of
3
+ * [Tree-sitter](https://tree-sitter.github.io).
4
+ *
5
+ * Mark the rules of a grammar that should appear in the tree with
6
+ * {@link node} (and, optionally, {@link token} and {@link field}). Outside
7
+ * this module the marks change nothing: `Parser.parse` returns the same
8
+ * values, and compiled parsers compile them away. {@link parse} runs the
9
+ * grammar and returns a {@link Tree}: a tree of {@link SyntaxNode}s with
10
+ * spans, parents and siblings, to search by position (`descendantAt`) or walk
11
+ * with a {@link TreeCursor}. Each node also carries the value its parser
12
+ * returned.
13
+ *
14
+ * Like Tree-sitter, a syntax parse always produces a tree. Failures handled
15
+ * by `Parser.resumeAfter` and `Parser.retryAfter` become `ERROR` nodes over
16
+ * the input they skipped, and so does any input after a fatal failure.
17
+ *
18
+ * After an edit, {@link reparse} (or {@link replace}) parses the new input
19
+ * reusing every node of the old tree whose input is unchanged: only the
20
+ * nodes that enclose the edit, or looked at the edited input, are parsed
21
+ * again, and each reused node is stepped over whole.
22
+ *
23
+ * @example
24
+ * ```ts
25
+ * import { Ascii, Parser, Syntax, Token } from "@thomasfosterau/effect-parse"
26
+ *
27
+ * const ws = Ascii.multispace0
28
+ * const number = Syntax.node(Ascii.decInt, "number")
29
+ * const list = Syntax.node(
30
+ * Parser.delimited(
31
+ * Token.literal("["),
32
+ * Parser.separated(Parser.delimited(ws, number, ws), Token.literal(",")),
33
+ * Token.literal("]")
34
+ * ),
35
+ * "list"
36
+ * )
37
+ *
38
+ * const tree = Syntax.parse(list, "[1, 2, 3]")
39
+ * String(tree.root) // "(list (number) (number) (number))"
40
+ * tree.root.descendantAt(4)?.text // "2"
41
+ *
42
+ * const edited = Syntax.replace(tree, 4, 5, "20") // "[1, 20, 3]"
43
+ * edited.root.namedChild(1)?.value // 20
44
+ * edited.reused // 2: only "20" was parsed again
45
+ * ```
46
+ *
47
+ * @since 0.2.0
48
+ */
49
+ import * as Predicate from "effect/Predicate"
50
+ import type * as Context from "effect/Context"
51
+ import { dual } from "effect/Function"
52
+ import * as Option from "effect/Option"
53
+ import type { Input, Slice, Source } from "./Input.js"
54
+ import { Fail, fromSource } from "./Input.js"
55
+ import * as ParseError from "./ParseError.js"
56
+ import { nodeOf } from "./internal/node.js"
57
+ import * as Stack from "./internal/stack.js"
58
+ import type { ErrorInfo, Green, Previous, Segment } from "./internal/syntax.js"
59
+ import { absolute, errorKind, SyntaxState } from "./internal/syntax.js"
60
+ import type { Parser } from "./Parser.js"
61
+ import { described, isParser } from "./Parser.js"
62
+
63
+ // -----------------------------------------------------------------------------
64
+ // Grammar annotations
65
+ // -----------------------------------------------------------------------------
66
+
67
+ const syntaxNode = (self: Parser.Any, kind: string, named: boolean): Parser.Any => {
68
+ const step = self.step
69
+ // Identifies the nodes this parser builds, so a reparse can reuse them
70
+ const key = {}
71
+ return described<unknown, unknown, unknown, Input>(
72
+ (state) => {
73
+ const builder = state.syntax
74
+ return builder === undefined ? step(state) : builder.node(state, step, key, kind, named)
75
+ },
76
+ self.name,
77
+ { _tag: "Syntax", self, kind, named }
78
+ )
79
+ }
80
+
81
+ /**
82
+ * Marks a parser as a rule of the syntax tree: in a {@link parse}, each
83
+ * match becomes a named node of the given kind, spanning the input it
84
+ * consumed, with the nodes built inside it as children and its output as
85
+ * its `value`. Outside {@link parse}, the parser is unchanged.
86
+ *
87
+ * A match that consumes nothing adds no node (as in Tree-sitter, where rules
88
+ * can't match the empty string).
89
+ *
90
+ * For {@link reparse} to reuse a node, its parser's output must depend only
91
+ * on the input from where the node starts: not on services, on state kept
92
+ * in closures, or on absolute offsets (`Parser.location`, `Parser.span`),
93
+ * which are not updated when the node moves. Use the node's `start` and
94
+ * `end` instead.
95
+ *
96
+ * @since 0.2.0
97
+ * @category annotations
98
+ */
99
+ export const node: {
100
+ (kind: string): <A, E, R, I extends Input>(self: Parser<A, E, R, I>) => Parser<A, E, R, I>
101
+ <A, E, R, I extends Input>(self: Parser<A, E, R, I>, kind: string): Parser<A, E, R, I>
102
+ } = /*#__PURE__*/ dual(
103
+ 2,
104
+ <A, E, R, I extends Input>(self: Parser<A, E, R, I>, kind: string): Parser<A, E, R, I> =>
105
+ syntaxNode(self, kind, true) as Parser<A, E, R, I>
106
+ )
107
+
108
+ /**
109
+ * Like {@link node}, but builds an *anonymous* node, Tree-sitter's name for
110
+ * punctuation and keywords: anonymous nodes are left out of
111
+ * `namedChildren` and of the tree's printed form. The kind defaults to the
112
+ * literal for a `Token.literal` of a string, and to the parser's name
113
+ * otherwise.
114
+ *
115
+ * @example
116
+ * ```ts
117
+ * import { Syntax, Token } from "@thomasfosterau/effect-parse"
118
+ *
119
+ * const comma = Syntax.token(Token.literal(",")) // kind ","
120
+ * ```
121
+ *
122
+ * @since 0.2.0
123
+ * @category annotations
124
+ */
125
+ export const token: {
126
+ (kind?: string): <A, E, R, I extends Input>(self: Parser<A, E, R, I>) => Parser<A, E, R, I>
127
+ <A, E, R, I extends Input>(self: Parser<A, E, R, I>, kind?: string): Parser<A, E, R, I>
128
+ } = /*#__PURE__*/ dual(
129
+ (args) => isParser(args[0]),
130
+ <A, E, R, I extends Input>(self: Parser<A, E, R, I>, kind?: string): Parser<A, E, R, I> =>
131
+ syntaxNode(self, kind ?? defaultTokenKind(self), false) as Parser<A, E, R, I>
132
+ )
133
+
134
+ const defaultTokenKind = (self: Parser.Any): string => {
135
+ const n = nodeOf(self)
136
+ if (n._tag === "Literal" && Predicate.isString(n.literal) && !n.caseless) return n.literal
137
+ return self.name ?? "token"
138
+ }
139
+
140
+ /**
141
+ * Gives the nodes built by a parser a field name in their parent, like
142
+ * Tree-sitter's `field(name, rule)`: find them with `childByField` and
143
+ * `childrenByField`. Outside {@link parse}, the parser is unchanged.
144
+ *
145
+ * @example
146
+ * ```ts
147
+ * import { Ascii, Parser, Syntax, Token } from "@thomasfosterau/effect-parse"
148
+ *
149
+ * const name = Syntax.node(Ascii.alpha1, "name")
150
+ * const number = Syntax.node(Ascii.decInt, "number")
151
+ * const pair = Syntax.node(
152
+ * Parser.separatedPair(Syntax.field(name, "key"), Token.literal("="), Syntax.field(number, "value")),
153
+ * "pair"
154
+ * )
155
+ * Syntax.parse(pair, "x=1").root.childByField("value")?.value // 1
156
+ * ```
157
+ *
158
+ * @since 0.2.0
159
+ * @category annotations
160
+ */
161
+ export const field: {
162
+ (name: string): <A, E, R, I extends Input>(self: Parser<A, E, R, I>) => Parser<A, E, R, I>
163
+ <A, E, R, I extends Input>(self: Parser<A, E, R, I>, name: string): Parser<A, E, R, I>
164
+ } = /*#__PURE__*/ dual(2, <A, E, R, I extends Input>(self: Parser<A, E, R, I>, name: string): Parser<A, E, R, I> => {
165
+ const step = self.step
166
+ return described<A, E, R, I>(
167
+ (state) => {
168
+ const builder = state.syntax
169
+ return (builder === undefined ? step(state) : builder.field(state, step, name)) as A | typeof Fail
170
+ },
171
+ self.name,
172
+ { _tag: "Syntax", self, kind: undefined, named: false }
173
+ )
174
+ })
175
+
176
+ // -----------------------------------------------------------------------------
177
+ // Trees
178
+ // -----------------------------------------------------------------------------
179
+
180
+ /**
181
+ * The kind of the nodes that stand for input that could not be parsed.
182
+ *
183
+ * @since 0.2.0
184
+ * @category models
185
+ */
186
+ export const ERROR: "ERROR" = errorKind
187
+
188
+ /**
189
+ * An edit to an input, as Tree-sitter's `TSInputEdit`: the input
190
+ * `[start, oldEnd)` was replaced by `[start, newEnd)`. Offsets are in the
191
+ * input's units (UTF-16 code units for strings, bytes, elements).
192
+ *
193
+ * @since 0.2.0
194
+ * @category models
195
+ */
196
+ export interface Edit {
197
+ readonly start: number
198
+ readonly oldEnd: number
199
+ readonly newEnd: number
200
+ }
201
+
202
+ /**
203
+ * Options for {@link parse}.
204
+ *
205
+ * @since 0.2.0
206
+ * @category models
207
+ */
208
+ export interface ParseOptions {
209
+ /**
210
+ * The kind of the root node, when the grammar itself is not a
211
+ * {@link node}. Defaults to `"document"`.
212
+ */
213
+ readonly kind?: string | undefined
214
+ /**
215
+ * For {@link reparse}: how many input units custom parsers may read past
216
+ * the furthest position they move the cursor to or record a failure at,
217
+ * besides one token there. Defaults to 0, which is right for the built-in
218
+ * parsers, and for custom parsers (`Parser.make`) that only read past
219
+ * where they stop to see that the token there doesn't match.
220
+ *
221
+ * A node is reused only when the input its parse looked at is unchanged.
222
+ * Built-in parsers that read further, such as a literal that fails part
223
+ * way or `Token.takeUntil`, record how far they read.
224
+ */
225
+ readonly lookahead?: number | undefined
226
+ }
227
+
228
+ /**
229
+ * Options for {@link parse} with a parser that requires services.
230
+ *
231
+ * @since 0.2.0
232
+ * @category models
233
+ */
234
+ export interface ParseOptionsWithContext<R> extends ParseOptions {
235
+ readonly context: Context.Context<R>
236
+ }
237
+
238
+ interface Config {
239
+ readonly kind: string
240
+ readonly inner: Parser.Any
241
+ readonly lookahead: number
242
+ readonly context: Context.Context<never> | undefined
243
+ }
244
+
245
+ /**
246
+ * The result of a {@link parse}: a syntax tree over the whole input, the
247
+ * grammar's output, and the errors met on the way.
248
+ *
249
+ * @since 0.2.0
250
+ * @category models
251
+ */
252
+ export class Tree<out A, out E = never, I extends Input = string> {
253
+ /** The root node. It always spans the whole input. */
254
+ readonly root: SyntaxNode<I>
255
+ private lines: Array<number> | undefined
256
+ private errorList: ReadonlyArray<ParseError.ParseError<E>> | undefined
257
+
258
+ /** @internal */
259
+ constructor(
260
+ /** The input the tree was parsed from. */
261
+ readonly input: I,
262
+ /** The grammar's output: `none` when the parse failed. */
263
+ readonly value: Option.Option<A>,
264
+ /** How many nodes were reused from the previous tree by {@link reparse}. */
265
+ readonly reused: number,
266
+ /** @internal */
267
+ readonly green: Green,
268
+ /** @internal */
269
+ readonly config: Config,
270
+ /** @internal */
271
+ readonly encoding: ParseError.ParseError<never>["encoding"]
272
+ ) {
273
+ this.root = new SyntaxNode(this, green, 0, undefined, 0)
274
+ }
275
+
276
+ /**
277
+ * Every error of the parse, in input order: the failures recovered by
278
+ * `Parser.resumeAfter` and `Parser.retryAfter`, then the fatal one, if
279
+ * the parse failed. Each one belongs to an `ERROR` node.
280
+ */
281
+ get errors(): ReadonlyArray<ParseError.ParseError<E>> {
282
+ if (this.errorList === undefined) {
283
+ const out: Array<ParseError.ParseError<E>> = []
284
+ const visit = (green: Green, start: number) => {
285
+ if (!green.hasError) return
286
+ if (green.error !== undefined) out.push(this.toError(green.error, start))
287
+ for (let i = 0; i < green.children.length; i++) visit(green.children[i], start + green.offsets[i])
288
+ }
289
+ visit(this.green, 0)
290
+ this.errorList = out
291
+ }
292
+ return this.errorList
293
+ }
294
+
295
+ /** Whether the tree has any `ERROR` node. */
296
+ get hasErrors(): boolean {
297
+ return this.green.hasError
298
+ }
299
+
300
+ /** The smallest node containing `[start, end)` (see {@link SyntaxNode.descendantAt}). */
301
+ descendantAt(start: number, end?: number): SyntaxNode<I> {
302
+ return this.root.descendantAt(start, end)
303
+ }
304
+
305
+ /** A cursor at the root. */
306
+ walk(): TreeCursor<I> {
307
+ return new TreeCursor(this.root)
308
+ }
309
+
310
+ /**
311
+ * The 1-based line and column (in code points) of an offset, as in
312
+ * `ParseError`. Lines of a string input are indexed once, so lookups take
313
+ * logarithmic time.
314
+ */
315
+ lineColumn(offset: number): ParseError.LineColumn {
316
+ const input = this.input
317
+ if (!Predicate.isString(input)) return ParseError.lineColumn(input, offset, this.encoding)
318
+ let lines = this.lines
319
+ if (lines === undefined) {
320
+ lines = [0]
321
+ for (let i = input.indexOf("\n"); i >= 0; i = input.indexOf("\n", i + 1)) lines.push(i + 1)
322
+ this.lines = lines
323
+ }
324
+ const at = Math.max(0, Math.min(offset, input.length))
325
+ let lo = 0
326
+ let hi = lines.length - 1
327
+ while (lo < hi) {
328
+ const mid = (lo + hi + 1) >> 1
329
+ if (lines[mid] <= at) lo = mid
330
+ else hi = mid - 1
331
+ }
332
+ const lineStart = lines[lo]
333
+ return { line: lo + 1, column: ParseError.lineColumn(input.slice(lineStart, at), at - lineStart).column }
334
+ }
335
+
336
+ /** The root's S-expression (see {@link SyntaxNode.toString}). */
337
+ toString(): string {
338
+ return this.root.toString()
339
+ }
340
+
341
+ /** @internal */
342
+ toError(error: ErrorInfo, start: number): ParseError.ParseError<E> {
343
+ return ParseError.fromFailure<E>(this.input, absolute(error.failure, start), error.mode, this.encoding)
344
+ }
345
+ }
346
+
347
+ /**
348
+ * A node of a syntax tree: its kind, span and value, and its place in the
349
+ * tree. Nodes are views built on demand; two views of the same node are
350
+ * different objects but have the same `start`, `end` and `kind`.
351
+ *
352
+ * @since 0.2.0
353
+ * @category models
354
+ */
355
+ export class SyntaxNode<I extends Input = string> {
356
+ private childList: ReadonlyArray<SyntaxNode<I>> | undefined
357
+
358
+ /** @internal */
359
+ constructor(
360
+ /** The tree this node belongs to. */
361
+ readonly tree: Tree<unknown, unknown, I>,
362
+ /** @internal */
363
+ readonly green: Green,
364
+ /** The offset where the node starts. */
365
+ readonly start: number,
366
+ /** The parent node; `undefined` for the root. */
367
+ readonly parent: SyntaxNode<I> | undefined,
368
+ /** The node's index among its parent's children. */
369
+ readonly index: number
370
+ ) {}
371
+
372
+ /** The node's kind: the name given to {@link node} or {@link token}, or `"ERROR"`. */
373
+ get kind(): string {
374
+ return this.green.kind
375
+ }
376
+
377
+ /** `false` for the anonymous nodes built by {@link token}. */
378
+ get named(): boolean {
379
+ return this.green.named
380
+ }
381
+
382
+ /** The field name given by {@link field}, if any. */
383
+ get field(): string | undefined {
384
+ return this.parent?.green.fields?.[this.index]
385
+ }
386
+
387
+ /** The offset where the node ends (exclusive). */
388
+ get end(): number {
389
+ return this.start + this.green.width
390
+ }
391
+
392
+ /** The number of input units the node spans. */
393
+ get length(): number {
394
+ return this.green.width
395
+ }
396
+
397
+ /** The output of the node's parser (`undefined` for `ERROR` nodes, and for a root that failed). */
398
+ get value(): unknown {
399
+ return this.green.value
400
+ }
401
+
402
+ /** The input the node spans. */
403
+ get text(): Slice<I> {
404
+ const input = this.tree.input as any
405
+ return input instanceof Uint8Array
406
+ ? (input.subarray(this.start, this.end) as any)
407
+ : input.slice(this.start, this.end)
408
+ }
409
+
410
+ /** Whether this is an `ERROR` node. */
411
+ get isError(): boolean {
412
+ return this.green.error !== undefined
413
+ }
414
+
415
+ /** Whether this node or a descendant is an `ERROR` node. */
416
+ get hasError(): boolean {
417
+ return this.green.hasError
418
+ }
419
+
420
+ /** For an `ERROR` node, the error it stands for. */
421
+ get error(): ParseError.ParseError<unknown> | undefined {
422
+ const error = this.green.error
423
+ return error === undefined ? undefined : this.tree.toError(error, this.start)
424
+ }
425
+
426
+ /** The child nodes, named and anonymous, in input order. */
427
+ get children(): ReadonlyArray<SyntaxNode<I>> {
428
+ if (this.childList === undefined) {
429
+ const green = this.green
430
+ const out = new Array<SyntaxNode<I>>(green.children.length)
431
+ for (let i = 0; i < out.length; i++) out[i] = this.makeChild(i)
432
+ this.childList = out
433
+ }
434
+ return this.childList
435
+ }
436
+
437
+ /** The number of children. */
438
+ get childCount(): number {
439
+ return this.green.children.length
440
+ }
441
+
442
+ /** The child at `index` (negative indices count from the end). */
443
+ child(index: number): SyntaxNode<I> | undefined {
444
+ const n = this.green.children.length
445
+ const i = index < 0 ? n + index : index
446
+ return i >= 0 && i < n ? this.children[i] : undefined
447
+ }
448
+
449
+ /** The named children. */
450
+ get namedChildren(): ReadonlyArray<SyntaxNode<I>> {
451
+ return this.children.filter((c) => c.named)
452
+ }
453
+
454
+ /** The named child at `index`. */
455
+ namedChild(index: number): SyntaxNode<I> | undefined {
456
+ return this.namedChildren[index]
457
+ }
458
+
459
+ /** The first child with the given field name. */
460
+ childByField(name: string): SyntaxNode<I> | undefined {
461
+ const fields = this.green.fields
462
+ if (fields === undefined) return undefined
463
+ const i = fields.indexOf(name)
464
+ return i < 0 ? undefined : this.children[i]
465
+ }
466
+
467
+ /** The children with the given field name. */
468
+ childrenByField(name: string): ReadonlyArray<SyntaxNode<I>> {
469
+ const fields = this.green.fields
470
+ if (fields === undefined) return []
471
+ return this.children.filter((_, i) => fields[i] === name)
472
+ }
473
+
474
+ /** The next sibling, named or anonymous. */
475
+ get nextSibling(): SyntaxNode<I> | undefined {
476
+ return this.parent?.children[this.index + 1]
477
+ }
478
+
479
+ /** The previous sibling, named or anonymous. */
480
+ get previousSibling(): SyntaxNode<I> | undefined {
481
+ return this.index > 0 ? this.parent?.children[this.index - 1] : undefined
482
+ }
483
+
484
+ /** The next named sibling. */
485
+ get nextNamedSibling(): SyntaxNode<I> | undefined {
486
+ const siblings = this.parent?.children
487
+ if (siblings === undefined) return undefined
488
+ for (let i = this.index + 1; i < siblings.length; i++) if (siblings[i].named) return siblings[i]
489
+ return undefined
490
+ }
491
+
492
+ /** The previous named sibling. */
493
+ get previousNamedSibling(): SyntaxNode<I> | undefined {
494
+ const siblings = this.parent?.children
495
+ if (siblings === undefined) return undefined
496
+ for (let i = this.index - 1; i >= 0; i--) if (siblings[i].named) return siblings[i]
497
+ return undefined
498
+ }
499
+
500
+ /**
501
+ * The index of the child containing the input unit at `offset`, or -1.
502
+ * Takes logarithmic time.
503
+ */
504
+ childIndexAt(offset: number): number {
505
+ const green = this.green
506
+ const offsets = green.offsets
507
+ const rel = offset - this.start
508
+ let lo = 0
509
+ let hi = offsets.length - 1
510
+ let found = -1
511
+ while (lo <= hi) {
512
+ const mid = (lo + hi) >> 1
513
+ if (offsets[mid] <= rel) {
514
+ found = mid
515
+ lo = mid + 1
516
+ } else {
517
+ hi = mid - 1
518
+ }
519
+ }
520
+ // Skip back over empty `ERROR` nodes at `offset`
521
+ while (found >= 0 && green.children[found].width === 0 && offsets[found] === rel) found--
522
+ return found >= 0 && rel < offsets[found] + green.children[found].width ? found : -1
523
+ }
524
+
525
+ /** The child containing the input unit at `offset`. */
526
+ childAt(offset: number): SyntaxNode<I> | undefined {
527
+ const i = this.childIndexAt(offset)
528
+ return i < 0 ? undefined : this.children[i]
529
+ }
530
+
531
+ /**
532
+ * The smallest node, this one or a descendant, that contains
533
+ * `[start, end)` (Tree-sitter's `descendant_for_byte_range`). With only
534
+ * `start`, the deepest node containing the input unit at `start`. Takes
535
+ * time proportional to the depth of the tree times the logarithm of the
536
+ * number of children.
537
+ */
538
+ descendantAt(start: number, end: number = start + 1): SyntaxNode<I> {
539
+ return descendantOf(this, start, end)
540
+ }
541
+
542
+ /** Like {@link descendantAt}, but only stops at named nodes. */
543
+ namedDescendantAt(start: number, end: number = start + 1): SyntaxNode<I> {
544
+ let node: SyntaxNode<I> = this.descendantAt(start, end)
545
+ // Every node below `this` has a parent, so the walk stops at `this` at the latest
546
+ while (!node.named && node !== this) node = node.parent ?? this
547
+ return node
548
+ }
549
+
550
+ /** This node and its descendants, in pre-order. */
551
+ *descendants(): Generator<SyntaxNode<I>, void, undefined> {
552
+ const cursor = this.walk()
553
+ while (true) {
554
+ yield cursor.node
555
+ if (cursor.gotoFirstChild()) continue
556
+ while (!cursor.gotoNextSibling()) {
557
+ if (!cursor.gotoParent()) return
558
+ }
559
+ }
560
+ }
561
+
562
+ /** The descendants of the given kind, in pre-order. */
563
+ *descendantsOfKind(kind: string): Generator<SyntaxNode<I>, void, undefined> {
564
+ for (const n of this.descendants()) if (n.kind === kind) yield n
565
+ }
566
+
567
+ /** A cursor at this node, which cannot move above it. */
568
+ walk(): TreeCursor<I> {
569
+ return new TreeCursor(this)
570
+ }
571
+
572
+ /**
573
+ * The node as an S-expression of its named descendants, with field
574
+ * names, as Tree-sitter prints it: `(pair key: (name) value: (number))`.
575
+ */
576
+ toString(): string {
577
+ let out = ""
578
+ const visit = (green: Green, field: string | undefined) => {
579
+ if (green.named || green === this.green) {
580
+ if (out.length > 0) out += " "
581
+ if (field !== undefined) out += `${field}: `
582
+ out += `(${green.kind}`
583
+ for (let i = 0; i < green.children.length; i++) visit(green.children[i], green.fields?.[i])
584
+ out += ")"
585
+ } else {
586
+ for (let i = 0; i < green.children.length; i++) visit(green.children[i], green.fields?.[i])
587
+ }
588
+ }
589
+ visit(this.green, this.field)
590
+ return out
591
+ }
592
+
593
+ toJSON(): unknown {
594
+ return { kind: this.kind, start: this.start, end: this.end }
595
+ }
596
+
597
+ private makeChild(i: number): SyntaxNode<I> {
598
+ const green = this.green
599
+ return new SyntaxNode(this.tree, green.children[i], this.start + green.offsets[i], this, i)
600
+ }
601
+ }
602
+
603
+ /** The smallest node, `root` or a descendant, that contains `[start, end)` (see `SyntaxNode.descendantAt`). */
604
+ const descendantOf = <I extends Input>(root: SyntaxNode<I>, start: number, end: number): SyntaxNode<I> => {
605
+ let node = root
606
+ while (true) {
607
+ const i = node.childIndexAt(start)
608
+ if (i < 0) return node
609
+ const child = node.children[i]
610
+ if (end > child.end) return node
611
+ node = child
612
+ }
613
+ }
614
+
615
+ /**
616
+ * A cursor for walking a tree, as Tree-sitter's `TreeCursor`. It starts at
617
+ * a node and cannot move above it.
618
+ *
619
+ * @since 0.2.0
620
+ * @category models
621
+ */
622
+ export class TreeCursor<I extends Input = string> {
623
+ private current: SyntaxNode<I>
624
+ private depth = 0
625
+
626
+ /** @internal */
627
+ constructor(node: SyntaxNode<I>) {
628
+ this.current = node
629
+ }
630
+
631
+ /** The node at the cursor. */
632
+ get node(): SyntaxNode<I> {
633
+ return this.current
634
+ }
635
+
636
+ /** The field name of the node at the cursor, if any. */
637
+ get field(): string | undefined {
638
+ return this.depth === 0 ? undefined : this.current.field
639
+ }
640
+
641
+ /** Moves to the first child; `false` if there is none. */
642
+ gotoFirstChild(): boolean {
643
+ return this.goto(this.current.child(0))
644
+ }
645
+
646
+ /** Moves to the last child; `false` if there is none. */
647
+ gotoLastChild(): boolean {
648
+ return this.goto(this.current.child(-1))
649
+ }
650
+
651
+ /** Moves to the child containing the input unit at `offset`; `false` if there is none. */
652
+ gotoFirstChildForOffset(offset: number): boolean {
653
+ return this.goto(this.current.childAt(offset))
654
+ }
655
+
656
+ /** Moves to the next sibling; `false` if there is none. */
657
+ gotoNextSibling(): boolean {
658
+ if (this.depth === 0) return false
659
+ const next = this.current.nextSibling
660
+ if (next === undefined) return false
661
+ this.current = next
662
+ return true
663
+ }
664
+
665
+ /** Moves to the previous sibling; `false` if there is none. */
666
+ gotoPreviousSibling(): boolean {
667
+ if (this.depth === 0) return false
668
+ const previous = this.current.previousSibling
669
+ if (previous === undefined) return false
670
+ this.current = previous
671
+ return true
672
+ }
673
+
674
+ /** Moves to the parent; `false` at the node the cursor started at. */
675
+ gotoParent(): boolean {
676
+ const parent = this.current.parent
677
+ if (this.depth === 0 || parent === undefined) return false
678
+ this.current = parent
679
+ this.depth--
680
+ return true
681
+ }
682
+
683
+ /** Moves to the deepest node containing the input unit at `offset`. */
684
+ gotoDescendantAt(offset: number): void {
685
+ while (this.gotoFirstChildForOffset(offset)) {
686
+ // descend
687
+ }
688
+ }
689
+
690
+ /** Starts again at `node`. */
691
+ reset(node: SyntaxNode<I>): void {
692
+ this.current = node
693
+ this.depth = 0
694
+ }
695
+
696
+ private goto(node: SyntaxNode<I> | undefined): boolean {
697
+ if (node === undefined) return false
698
+ this.current = node
699
+ this.depth++
700
+ return true
701
+ }
702
+ }
703
+
704
+ // -----------------------------------------------------------------------------
705
+ // Parsing
706
+ // -----------------------------------------------------------------------------
707
+
708
+ const expectedEof = ["end of input"]
709
+
710
+ const run = <A, E, I extends Input>(config: Config, input: I, previous: Previous | undefined): Tree<A, E, I> => {
711
+ const state = new SyntaxState<I>(input, { context: config.context }, previous)
712
+ const a = Stack.run(config.inner.step, state)
713
+ // Text parsers record the encoding when they fail, which they may have done inside reused nodes
714
+ if (state.encoding === undefined && previous !== undefined) state.encoding = previous.encoding
715
+ let fatal: ErrorInfo | undefined
716
+ let value: Option.Option<A> = Option.none()
717
+ if (a === Fail) {
718
+ fatal = { failure: state.reportedFailure(), mode: state.mode }
719
+ } else if (state.pos < state.end) {
720
+ state.backtrack("Eof", expectedEof)
721
+ fatal = { failure: state.reportedFailure(), mode: "Backtrack" }
722
+ } else {
723
+ value = Option.some(a as A)
724
+ }
725
+ const green = state.finish(config.kind, Option.getOrUndefined(value), fatal, state.pos)
726
+ return new Tree<A, E, I>(input, value, state.reused, green, config, state.encoding)
727
+ }
728
+
729
+ /**
730
+ * Parses an input into a syntax {@link Tree}. The grammar's output is the
731
+ * tree's `value`, and its {@link node}s are the tree's nodes. When the
732
+ * grammar is itself a {@link node}, that node is the root; otherwise the
733
+ * root is a node of kind `options.kind` (by default `"document"`).
734
+ *
735
+ * The parse never fails: recovered failures (see `Parser.resumeAfter`), a
736
+ * fatal failure and unparsed input at the end all become `ERROR` nodes, and
737
+ * are listed in `tree.errors`. The parse is interpreted, never compiled.
738
+ *
739
+ * @since 0.2.0
740
+ * @category parsing
741
+ */
742
+ export const parse: {
743
+ <A, E, I extends Input>(
744
+ grammar: Parser<A, E, never, I>,
745
+ input: NoInfer<Source<I>>,
746
+ options?: ParseOptions
747
+ ): Tree<A, E, I>
748
+ <A, E, R, I extends Input>(
749
+ grammar: Parser<A, E, R, I>,
750
+ input: NoInfer<Source<I>>,
751
+ options: ParseOptionsWithContext<R>
752
+ ): Tree<A, E, I>
753
+ } = <A, E, R, I extends Input>(
754
+ grammar: Parser<A, E, R, I>,
755
+ input: I | Source<I>,
756
+ options?: ParseOptions & { readonly context?: Context.Context<R> }
757
+ ): Tree<A, E, I> => {
758
+ const n = nodeOf(grammar)
759
+ const isRoot = n._tag === "Syntax" && n.kind !== undefined && n.named
760
+ const config: Config = {
761
+ kind: isRoot ? n.kind : (options?.kind ?? "document"),
762
+ inner: isRoot ? (n.self as Parser.Any) : grammar,
763
+ lookahead: options?.lookahead ?? 0,
764
+ context: options?.context as Context.Context<never> | undefined
765
+ }
766
+ return run(config, fromSource(input as Input) as I, undefined)
767
+ }
768
+
769
+ /**
770
+ * Parses the new version of a tree's input, reusing the nodes of the tree
771
+ * that the edits leave unchanged (Tree-sitter's `ts_tree_edit` followed by
772
+ * `ts_parser_parse` with the old tree). The result is the tree a fresh
773
+ * {@link parse} of `input` would build, but only the parts of the tree
774
+ * around the edits are parsed again. `tree.reused` says how many nodes were
775
+ * reused.
776
+ *
777
+ * `edits` describe how the old input became the new one, applied in order,
778
+ * each in the coordinates of the input as the previous edits left it. When
779
+ * they are omitted, the edit is found by comparing the inputs (their common
780
+ * prefix and suffix), which takes time linear in their length but much less
781
+ * than parsing.
782
+ *
783
+ * Errors in the new tree are those a fresh parse reports, except that an
784
+ * error just after a reused node may list fewer expected alternatives.
785
+ *
786
+ * @example
787
+ * ```ts
788
+ * import { Ascii, Syntax } from "@thomasfosterau/effect-parse"
789
+ *
790
+ * const number = Syntax.node(Ascii.decInt, "number")
791
+ * const tree = Syntax.parse(number, "12")
792
+ * Syntax.reparse(tree, "123", [{ start: 2, oldEnd: 2, newEnd: 3 }]).root.value // 123
793
+ * ```
794
+ *
795
+ * @since 0.2.0
796
+ * @category parsing
797
+ */
798
+ export const reparse = <A, E, I extends Input>(
799
+ tree: Tree<A, E, I>,
800
+ input: NoInfer<Source<I>>,
801
+ edits?: Edit | ReadonlyArray<Edit>
802
+ ): Tree<A, E, I> => {
803
+ const next = fromSource(input as Input) as I
804
+ const list =
805
+ edits === undefined
806
+ ? diffList(tree.input, next)
807
+ : "start" in edits
808
+ ? [edits as Edit]
809
+ : (edits as ReadonlyArray<Edit>)
810
+ const previous: Previous = {
811
+ root: tree.green,
812
+ oldLength: tree.input.length,
813
+ newLength: next.length,
814
+ segments: segmentsOf(tree.input.length, next.length, list),
815
+ input: tree.input,
816
+ extra: tree.config.lookahead,
817
+ encoding: tree.encoding
818
+ }
819
+ return run(tree.config, next, previous)
820
+ }
821
+
822
+ /**
823
+ * Replaces `[start, end)` of a tree's string input with `text` and parses
824
+ * the result incrementally (see {@link reparse}).
825
+ *
826
+ * @since 0.2.0
827
+ * @category parsing
828
+ */
829
+ export const replace = <A, E>(
830
+ tree: Tree<A, E, string>,
831
+ start: number,
832
+ end: number,
833
+ text: string
834
+ ): Tree<A, E, string> => {
835
+ const input = tree.input
836
+ if (start < 0 || end < start || end > input.length) {
837
+ throw new RangeError(`invalid range [${start}, ${end}) of an input of length ${input.length}`)
838
+ }
839
+ return reparse(tree, input.slice(0, start) + text + input.slice(end), {
840
+ start,
841
+ oldEnd: end,
842
+ newEnd: start + text.length
843
+ })
844
+ }
845
+
846
+ /**
847
+ * Finds the edit that turns `before` into `after`: the part between their
848
+ * longest common prefix and suffix. `undefined` when they are equal.
849
+ *
850
+ * @since 0.2.0
851
+ * @category parsing
852
+ */
853
+ export const diff = <I extends Input>(before: I, after: I): Edit | undefined => {
854
+ const a = before as any
855
+ const b = after as any
856
+ const at = Predicate.isString(a) ? (s: string, i: number) => s.charCodeAt(i) : (s: any, i: number) => s[i]
857
+ const max = Math.min(a.length, b.length)
858
+ let prefix = 0
859
+ while (prefix < max && at(a, prefix) === at(b, prefix)) prefix++
860
+ if (prefix === a.length && prefix === b.length) return undefined
861
+ let suffix = 0
862
+ while (suffix < max - prefix && at(a, a.length - 1 - suffix) === at(b, b.length - 1 - suffix)) suffix++
863
+ return { start: prefix, oldEnd: a.length - suffix, newEnd: b.length - suffix }
864
+ }
865
+
866
+ const diffList = (before: Input, after: Input): ReadonlyArray<Edit> => {
867
+ const edit = diff(before, after)
868
+ return edit === undefined ? [] : [edit]
869
+ }
870
+
871
+ /** The stretches of the new input copied unchanged from the old one. */
872
+ const segmentsOf = (oldLength: number, newLength: number, edits: ReadonlyArray<Edit>): Array<Segment> => {
873
+ let segments: Array<Segment> = oldLength > 0 ? [{ newStart: 0, oldStart: 0, length: oldLength }] : []
874
+ let length = oldLength
875
+ for (const { newEnd, oldEnd, start } of edits) {
876
+ if (!(start >= 0 && oldEnd >= start && oldEnd <= length && newEnd >= start)) {
877
+ throw new RangeError(
878
+ `invalid edit { start: ${start}, oldEnd: ${oldEnd}, newEnd: ${newEnd} } of an input of length ${length}`
879
+ )
880
+ }
881
+ const delta = newEnd - oldEnd
882
+ const out: Array<Segment> = []
883
+ for (const s of segments) {
884
+ const end = s.newStart + s.length
885
+ if (s.newStart < start) {
886
+ out.push({ newStart: s.newStart, oldStart: s.oldStart, length: Math.min(end, start) - s.newStart })
887
+ }
888
+ if (end > oldEnd) {
889
+ const from = Math.max(s.newStart, oldEnd)
890
+ out.push({ newStart: from + delta, oldStart: s.oldStart + (from - s.newStart), length: end - from })
891
+ }
892
+ }
893
+ segments = out
894
+ length += delta
895
+ }
896
+ if (length !== newLength) {
897
+ throw new RangeError(`the edits give an input of length ${length}, but the new input has length ${newLength}`)
898
+ }
899
+ return segments
900
+ }