@amritk/nish-x86_64-linux 0.20.0 → 0.22.1

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 (74) hide show
  1. package/INSTALL.md +52 -10
  2. package/bin/nish +0 -0
  3. package/docs/AI.md +2777 -0
  4. package/package.json +2 -1
  5. package/runtime/browser.d.ts +6 -0
  6. package/runtime/browser.mjs +77 -0
  7. package/runtime/core/arrays.d.ts +36 -0
  8. package/runtime/core/arrays.mjs +153 -0
  9. package/runtime/core/checks.mjs +20 -0
  10. package/runtime/core/host.d.ts +80 -0
  11. package/runtime/core/host.mjs +325 -0
  12. package/runtime/core/numbers.d.ts +66 -0
  13. package/runtime/core/numbers.mjs +343 -0
  14. package/runtime/core/result.d.ts +45 -0
  15. package/runtime/core/result.mjs +140 -0
  16. package/runtime/core/strings.d.ts +14 -0
  17. package/runtime/core/strings.mjs +169 -0
  18. package/runtime/core.d.ts +9 -0
  19. package/runtime/core.mjs +55 -0
  20. package/runtime/host.d.ts +6 -0
  21. package/runtime/nish.d.ts +46 -2
  22. package/runtime/nish.h +75 -2
  23. package/runtime/nish.mjs +23 -3
  24. package/runtime/node.mjs +386 -0
  25. package/runtime/runtime-bytes.c +401 -0
  26. package/runtime/runtime-cross.c +1 -1
  27. package/runtime/runtime-host.c +1 -1
  28. package/runtime/runtime-net.c +12 -1
  29. package/runtime/runtime-os.c +1 -1
  30. package/runtime/runtime-parallel.c +1 -1
  31. package/runtime/runtime-simd.c +231 -7
  32. package/runtime/runtime-term.c +504 -0
  33. package/runtime/runtime-wasm.c +0 -11
  34. package/runtime/runtime.c +190 -6
  35. package/runtime/shim.mjs +6 -985
  36. package/runtime/sqlite/LICENSE.md +85 -0
  37. package/runtime/sqlite/README.md +45 -0
  38. package/runtime/sqlite/nish-sqlite.c +359 -0
  39. package/runtime/sqlite/sqlite3.c +271131 -0
  40. package/runtime/sqlite/sqlite3.h +14447 -0
  41. package/scripts/build.sh +311 -59
  42. package/std/LICENSE-fast-float +23 -0
  43. package/std/LICENSE-fdlibm +8 -0
  44. package/std/README.md +32 -12
  45. package/std/base64.ts +116 -0
  46. package/std/bytes.ts +256 -0
  47. package/std/cli.ts +344 -0
  48. package/std/collections.ts +6 -5
  49. package/std/crypto/LICENSE-crypt_blowfish +26 -0
  50. package/std/crypto/base64url.ts +125 -0
  51. package/std/crypto/bcrypt.ts +1557 -0
  52. package/std/crypto/hmac.ts +138 -2
  53. package/std/crypto/x509.ts +11 -122
  54. package/std/json-tokenizer.ts +2998 -57
  55. package/std/json-value.ts +22 -3
  56. package/std/json.ts +87 -8
  57. package/std/math.ts +608 -0
  58. package/std/net/http1-server.ts +282 -40
  59. package/std/net/http1.ts +40 -21
  60. package/std/net/http3-server.ts +50 -12
  61. package/std/net/postgres.ts +2294 -0
  62. package/std/net/prefork.ts +23 -3
  63. package/std/net/quic-listener.ts +20 -4
  64. package/std/random.ts +66 -4
  65. package/std/regex.ts +4274 -0
  66. package/std/sqlite.ts +391 -0
  67. package/std/term-io.ts +978 -0
  68. package/std/term-pixels.ts +874 -0
  69. package/std/term-probe.ts +459 -0
  70. package/std/term-rate.ts +993 -0
  71. package/std/term.ts +2453 -0
  72. package/std/text.ts +90 -7
  73. package/std/yaml-lines.ts +141 -0
  74. package/std/yaml.ts +4675 -0
package/std/yaml.ts ADDED
@@ -0,0 +1,4675 @@
1
+ /**
2
+ * `nish/yaml` — a YAML 1.2 parser that keeps where everything came from.
3
+ *
4
+ * import { parseYaml, YAML_MAP } from "nish/yaml"
5
+ *
6
+ * const stream = parseYaml(text)
7
+ * const root: i32 = stream.documentRoot(0)
8
+ * if (stream.kind(root) === YAML_MAP) {
9
+ * let i: i32 = 0
10
+ * while (i < stream.count(root)) {
11
+ * const pair: i32 = stream.child(root, i)
12
+ * // stream.key(pair), stream.value(pair), stream.text(node) ...
13
+ * i += 1
14
+ * }
15
+ * }
16
+ *
17
+ * A parse never fails. It answers a `YamlStream`: every document of the text,
18
+ * every node of every document, and every problem found on the way, each with
19
+ * the byte range of the source it is about. A program that only wants the data
20
+ * reads the nodes; a linter, an editor or a schema checker reads the ranges too,
21
+ * and `nish/yaml-lines` turns a range into the `line:column` a person reads.
22
+ *
23
+ * **A node is an `i32` handle** into the stream's arrays, one array per field
24
+ * (struct of arrays), and `-1` is no node. A node is a range of the source,
25
+ * nothing more, until it is asked for: a scalar's `text` is decoded, and its
26
+ * `type` resolved against the core schema, only when a caller reads them. So a
27
+ * parse allocates its arrays and its problems and very little else, and a
28
+ * caller that walks to one key pays to decode one key.
29
+ *
30
+ * The kinds are `YAML_SCALAR`, `YAML_MAP`, `YAML_SEQ`, `YAML_ALIAS` and
31
+ * `YAML_PAIR`. A map's children are its pairs, in source order; a pair's `key`
32
+ * and `value` are nodes. A seq's children are its items. An alias's `target`
33
+ * is the node its anchor named. A map holds the pairs it was written with, and
34
+ * merge keys (`<<`) are not copied into it: a `<<` key is exempt from the
35
+ * duplicate-key check, its value must be a map, an alias to one or a sequence
36
+ * of those, and `pathNode` and `pathPair` look a key a map lacks up in the maps
37
+ * it merges.
38
+ *
39
+ * **Diagnostics** carry a `YAML_ERR_*` code, whether they are a warning, a
40
+ * message, a byte range and the document they belong to, sorted by where they
41
+ * start. An error means the stream is not valid YAML and the nodes are the
42
+ * parser's best reading of it; a warning means it is valid and probably not what
43
+ * was meant. The codes and their spelling (`yamlErrorName`) are the ones other
44
+ * YAML tooling reports, so a message from here reads the same as one from there.
45
+ *
46
+ * Strings are UTF-8, offsets are bytes. Tabs are not indentation (YAML forbids
47
+ * it), and YAML 1.1's booleans (`yes`, `on`) are strings, as 1.2 says.
48
+ */
49
+
50
+ import { indexOfAny } from "nish/text"
51
+ import { wrappingAdd, wrappingMul, wrappingSub } from "nish:unsafe"
52
+
53
+ // ---- Node kinds, styles and types -------------------------------------------
54
+
55
+ /** A scalar: plain, quoted or a block scalar. */
56
+ export const YAML_SCALAR: i32 = 1
57
+ /** A mapping; its children are `YAML_PAIR` nodes. */
58
+ export const YAML_MAP: i32 = 2
59
+ /** A sequence; its children are its items. */
60
+ export const YAML_SEQ: i32 = 3
61
+ /** An alias (`*name`); `target` is the anchored node. */
62
+ export const YAML_ALIAS: i32 = 4
63
+ /** One key and value of a mapping. */
64
+ export const YAML_PAIR: i32 = 5
65
+
66
+ /** A plain scalar, or a node with no style of its own (an alias, a pair). */
67
+ export const YAML_PLAIN: i32 = 0
68
+ export const YAML_SINGLE_QUOTED: i32 = 1
69
+ export const YAML_DOUBLE_QUOTED: i32 = 2
70
+ /** A `|` block scalar. */
71
+ export const YAML_LITERAL: i32 = 3
72
+ /** A `>` block scalar. */
73
+ export const YAML_FOLDED: i32 = 4
74
+ /** A `[...]` or `{...}` collection, or the one-pair map `[a: b]` holds. */
75
+ export const YAML_FLOW: i32 = 5
76
+ /** An indented collection. */
77
+ export const YAML_BLOCK: i32 = 6
78
+
79
+ /** A scalar's core-schema type, from `type`; `-1` for a node that is not a scalar. */
80
+ export const YAML_NULL: i32 = 0
81
+ export const YAML_BOOL: i32 = 1
82
+ export const YAML_INT: i32 = 2
83
+ export const YAML_FLOAT: i32 = 3
84
+ export const YAML_STRING: i32 = 4
85
+
86
+ // ---- Diagnostic codes ---------------------------------------------------------
87
+
88
+ export const YAML_ERR_AMBIGUOUS_ANCHOR_NAME: i32 = 1
89
+ export const YAML_ERR_BAD_ANCHOR: i32 = 2
90
+ export const YAML_ERR_BAD_BLOCK_HEADER: i32 = 3
91
+ export const YAML_ERR_BAD_COMMENT: i32 = 4
92
+ export const YAML_ERR_BAD_DIRECTIVE: i32 = 5
93
+ export const YAML_ERR_BAD_ESCAPE: i32 = 6
94
+ export const YAML_ERR_BAD_IMPLICIT_KEY: i32 = 7
95
+ export const YAML_ERR_BAD_INDENT: i32 = 8
96
+ export const YAML_ERR_BAD_MERGE: i32 = 9
97
+ export const YAML_ERR_BAD_PROPERTY: i32 = 10
98
+ export const YAML_ERR_BAD_SCALAR_CONTENT: i32 = 11
99
+ export const YAML_ERR_BAD_SCALAR_START: i32 = 12
100
+ export const YAML_ERR_BAD_TAG: i32 = 13
101
+ export const YAML_ERR_BAD_TAG_VALUE: i32 = 14
102
+ export const YAML_ERR_DEPTH_LIMIT: i32 = 15
103
+ export const YAML_ERR_DUPLICATE_DIRECTIVE: i32 = 16
104
+ export const YAML_ERR_DUPLICATE_KEY: i32 = 17
105
+ export const YAML_ERR_MULTIPLE_DOCUMENTS: i32 = 18
106
+ export const YAML_ERR_RECURSIVE_ALIAS: i32 = 19
107
+ export const YAML_ERR_TAB_INDENT: i32 = 20
108
+ export const YAML_ERR_UNEXPECTED_COMMA: i32 = 21
109
+ export const YAML_ERR_UNEXPECTED_CONTENT: i32 = 22
110
+ export const YAML_ERR_UNEXPECTED_DIRECTIVE: i32 = 23
111
+ export const YAML_ERR_UNKNOWN_DIRECTIVE: i32 = 24
112
+ export const YAML_ERR_UNKNOWN_TAG_HANDLE: i32 = 25
113
+ export const YAML_ERR_UNRESOLVED_ALIAS: i32 = 26
114
+ export const YAML_ERR_UNSUPPORTED_YAML_VERSION: i32 = 27
115
+ export const YAML_ERR_UNTERMINATED_FLOW: i32 = 28
116
+ export const YAML_ERR_UNTERMINATED_QUOTE: i32 = 29
117
+
118
+ /** The name of a `YAML_ERR_*` code, such as `"DUPLICATE_KEY"`, or `""` for one that is not a code. */
119
+ export const yamlErrorName = (code: i32): string => {
120
+ const names: string[] = [
121
+ "",
122
+ "AMBIGUOUS_ANCHOR_NAME",
123
+ "BAD_ANCHOR",
124
+ "BAD_BLOCK_HEADER",
125
+ "BAD_COMMENT",
126
+ "BAD_DIRECTIVE",
127
+ "BAD_ESCAPE",
128
+ "BAD_IMPLICIT_KEY",
129
+ "BAD_INDENT",
130
+ "BAD_MERGE",
131
+ "BAD_PROPERTY",
132
+ "BAD_SCALAR_CONTENT",
133
+ "BAD_SCALAR_START",
134
+ "BAD_TAG",
135
+ "BAD_TAG_VALUE",
136
+ "DEPTH_LIMIT",
137
+ "DUPLICATE_DIRECTIVE",
138
+ "DUPLICATE_KEY",
139
+ "MULTIPLE_DOCUMENTS",
140
+ "RECURSIVE_ALIAS",
141
+ "TAB_INDENT",
142
+ "UNEXPECTED_COMMA",
143
+ "UNEXPECTED_CONTENT",
144
+ "UNEXPECTED_DIRECTIVE",
145
+ "UNKNOWN_DIRECTIVE",
146
+ "UNKNOWN_TAG_HANDLE",
147
+ "UNRESOLVED_ALIAS",
148
+ "UNSUPPORTED_YAML_VERSION",
149
+ "UNTERMINATED_FLOW",
150
+ "UNTERMINATED_QUOTE",
151
+ ]
152
+ return code >= 1 && code < toI32(names.length) ? names[code] : ""
153
+ }
154
+
155
+ // ---- Bytes -------------------------------------------------------------------
156
+
157
+ const YAML_TAB: i32 = 9
158
+ const YAML_LF: i32 = 10
159
+ const YAML_CR: i32 = 13
160
+ const YAML_SPACE: i32 = 32
161
+ const YAML_BANG: i32 = 33
162
+ const YAML_DQUOTE: i32 = 34
163
+ const YAML_HASH: i32 = 35
164
+ const YAML_PERCENT: i32 = 37
165
+ const YAML_AMP: i32 = 38
166
+ const YAML_SQUOTE: i32 = 39
167
+ const YAML_STAR: i32 = 42
168
+ const YAML_PLUS: i32 = 43
169
+ const YAML_COMMA: i32 = 44
170
+ const YAML_DASH: i32 = 45
171
+ const YAML_DOT: i32 = 46
172
+ const YAML_ZERO: i32 = 48
173
+ const YAML_ONE: i32 = 49
174
+ const YAML_NINE: i32 = 57
175
+ const YAML_COLON: i32 = 58
176
+ const YAML_LT: i32 = 60
177
+ const YAML_GT: i32 = 62
178
+ const YAML_QUESTION: i32 = 63
179
+ const YAML_AT: i32 = 64
180
+ const YAML_LBRACKET: i32 = 91
181
+ const YAML_BACKSLASH: i32 = 92
182
+ const YAML_RBRACKET: i32 = 93
183
+ const YAML_BACKTICK: i32 = 96
184
+ const YAML_LBRACE: i32 = 123
185
+ const YAML_PIPE: i32 = 124
186
+ const YAML_RBRACE: i32 = 125
187
+
188
+ /** The byte sets the scanners hand `indexOfAny`. */
189
+ const YAML_PLAIN_FLOW_STOPS: string = ":#,[]{}\n\r"
190
+ const YAML_DOUBLE_STOPS: string = '"\\\n\r'
191
+ const YAML_SINGLE_STOPS: string = "'\n\r"
192
+ const YAML_BREAKS: string = "\n\r"
193
+
194
+ /** The most bytes an implicit key may run before its `:` (YAML 1.2 §7.4.2). */
195
+ const YAML_MAX_KEY_LENGTH: i32 = 1024
196
+ /** The deepest `maxDepth` may be, and its default. */
197
+ const YAML_MAX_DEPTH: i32 = 1000
198
+ /** Past this many pairs a map's duplicate check hashes instead of comparing every pair with every other. */
199
+ const YAML_PAIRWISE_KEYS: i32 = 16
200
+ /** No node, no slot, no offset: what every lookup answers outside its array. */
201
+ const YAML_NONE: i32 = -1
202
+ /** What `type` holds for a scalar it has not resolved yet. */
203
+ const YAML_UNRESOLVED: i32 = 255
204
+ /** The prefix a `!!` tag stands for; tags are kept with it stripped (`"int"`). */
205
+ const YAML_CORE_PREFIX: string = "tag:yaml.org,2002:"
206
+
207
+ /** What `skipStreamHead` met before a document's body: a `---`, a `...`, and a node on the `---` line. */
208
+ const YAML_SAW_DOC_START: i32 = 1
209
+ const YAML_SAW_DOC_END: i32 = 2
210
+ const YAML_SAW_INLINE_NODE: i32 = 4
211
+
212
+ /** A block scalar's chomping, in the low two bits of its `extras`. */
213
+ const YAML_CLIP: i32 = 0
214
+ const YAML_STRIP: i32 = 1
215
+ const YAML_KEEP: i32 = 2
216
+
217
+ // ---- Options -------------------------------------------------------------------
218
+
219
+ /** How `parseYamlWith` reads a stream. Make one with `new YamlOptions()` and set what differs. */
220
+ export class YamlOptions {
221
+ /** How deeply collections may nest before `DEPTH_LIMIT`; 1 to 1000, and anything else panics. */
222
+ maxDepth: i32 = 1000
223
+ /** Whether a map that repeats a key is reported (`DUPLICATE_KEY`). */
224
+ uniqueKeys: boolean = true
225
+ /**
226
+ * Whether a plain `<<` key is a merge key: exempt from `uniqueKeys`, checked
227
+ * to name maps, and followed by paths. The nodes are not changed either way.
228
+ */
229
+ merge: boolean = true
230
+ /** Whether the stream keeps the byte range of every comment (`commentCount`). */
231
+ keepComments: boolean = false
232
+ /** Whether only the first document is read, with `MULTIPLE_DOCUMENTS` if more follow. */
233
+ firstDocumentOnly: boolean = false
234
+ }
235
+
236
+ // ---- The stream ----------------------------------------------------------------
237
+
238
+ /**
239
+ * Every document, node and diagnostic one parse found. Its fields and its
240
+ * "Internal" methods are reachable, as every member in Nish is; use the others.
241
+ *
242
+ * A read never panics: a handle that names no node (`-1`, or one past the
243
+ * stream), or a read that does not fit the node's kind (`count` of a scalar,
244
+ * `intValue` of a string), answers a default — `-1` for a node or an offset,
245
+ * `0`, `false` or `""` otherwise — as each method says.
246
+ */
247
+ export class YamlStream {
248
+ /** The text the stream was parsed from; every offset is into it. */
249
+ source: string
250
+ /** Per node: the first byte of its source, and one past its last. */
251
+ starts: i32[]
252
+ ends: i32[]
253
+ /**
254
+ * Per node, by kind: a collection's first child in `kids` and its child
255
+ * count; a pair's key and value; an alias's target and `-1`; a scalar's body
256
+ * (the bytes inside its quotes, or a block scalar's lines).
257
+ */
258
+ firsts: i32[]
259
+ counts: i32[]
260
+ /**
261
+ * Per node: a block scalar's content indent, whether it was written, and its
262
+ * chomping, as `(indent + 1) * 8 + explicit * 4 + chomp`.
263
+ */
264
+ extras: i32[]
265
+ /** Per node: its properties' slot, or `-1`. */
266
+ props: i32[]
267
+ /** Every collection's children, one collection after another. */
268
+ kids: i32[]
269
+ /** Per property slot: the anchor's name (after `&`), the tag as written, and the anchor table entry. */
270
+ propAnchorStarts: i32[]
271
+ propAnchorEnds: i32[]
272
+ propTagStarts: i32[]
273
+ propTagEnds: i32[]
274
+ propStarts: i32[]
275
+ propEnds: i32[]
276
+ propEntries: i32[]
277
+ /** Per property slot: the resolved tag, with `tag:yaml.org,2002:` stripped, or `""`. */
278
+ propTags: string[]
279
+ /** Per diagnostic: its code, range, document and message, and whether it is a warning. */
280
+ diagCodes: i32[]
281
+ diagStarts: i32[]
282
+ diagEnds: i32[]
283
+ diagDocuments: i32[]
284
+ diagMessages: string[]
285
+ diagWarnings: boolean[]
286
+ /** Per document: its root node and its range. */
287
+ docRoots: i32[]
288
+ docStarts: i32[]
289
+ docEnds: i32[]
290
+ /** Per comment kept: its range, from the `#`. */
291
+ commentStarts: i32[]
292
+ commentEnds: i32[]
293
+ /**
294
+ * Per node: the path step whose merge search last went through it, as
295
+ * `mergeStep` numbers them; grown to the node count by the first search, so
296
+ * a search marks a map searched in one store.
297
+ */
298
+ mergeStamps: i32[]
299
+ /** Per node: its `YAML_*` kind, style and core-schema type (`YAML_UNRESOLVED` until read). */
300
+ kinds: u8[]
301
+ styles: u8[]
302
+ types: u8[]
303
+
304
+ /** How many nodes the arrays have room for, and how many are in use. */
305
+ cap: i32 = 0
306
+ top: i32 = 0
307
+ /** How many slots of `kids` are in use. */
308
+ kidTop: i32 = 0
309
+ errors: i32 = 0
310
+ warnings: i32 = 0
311
+ /** How many path steps have searched merge keys; the stamp of the latest. */
312
+ mergeStep: i32 = 0
313
+ /** Whether a plain `<<` key is a merge key, as `YamlOptions.merge` said; paths follow merge keys while it is. */
314
+ merge: boolean = true
315
+
316
+ constructor(text: string) {
317
+ this.source = text
318
+ const room: i32 = toI32(text.length) / 8 + 16
319
+ this.cap = room
320
+ this.starts = new Array<i32>(room)
321
+ this.ends = new Array<i32>(room)
322
+ this.firsts = new Array<i32>(room)
323
+ this.counts = new Array<i32>(room)
324
+ this.extras = new Array<i32>(room)
325
+ this.props = new Array<i32>(room)
326
+ this.kids = new Array<i32>(room)
327
+ this.kinds = new Array<u8>(room)
328
+ this.styles = new Array<u8>(room)
329
+ this.types = new Array<u8>(room)
330
+ this.propAnchorStarts = []
331
+ this.propAnchorEnds = []
332
+ this.propTagStarts = []
333
+ this.propTagEnds = []
334
+ this.propStarts = []
335
+ this.propEnds = []
336
+ this.propEntries = []
337
+ this.propTags = []
338
+ this.diagCodes = []
339
+ this.diagStarts = []
340
+ this.diagEnds = []
341
+ this.diagDocuments = []
342
+ this.diagMessages = []
343
+ this.diagWarnings = []
344
+ this.docRoots = []
345
+ this.docStarts = []
346
+ this.docEnds = []
347
+ this.commentStarts = []
348
+ this.commentEnds = []
349
+ this.mergeStamps = []
350
+ }
351
+
352
+ // ---- Documents ---------------------------------------------------------------
353
+
354
+ /** How many documents the stream holds: `0` for an empty text, unless `firstDocumentOnly`. */
355
+ documentCount(): i32 {
356
+ return toI32(this.docRoots.length)
357
+ }
358
+
359
+ /** Document `d`'s root node, or `-1` for a document that is not there. */
360
+ documentRoot(d: i32): i32 {
361
+ return yamlAt(this.docRoots, d)
362
+ }
363
+
364
+ /** The byte document `d` starts at (its `---`, if it has one), or `-1`. */
365
+ documentStart(d: i32): i32 {
366
+ return yamlAt(this.docStarts, d)
367
+ }
368
+
369
+ /** One past document `d`'s last byte (through its `...`, if it has one), or `-1`. */
370
+ documentEnd(d: i32): i32 {
371
+ return yamlAt(this.docEnds, d)
372
+ }
373
+
374
+ // ---- Nodes -------------------------------------------------------------------
375
+
376
+ /** Node `n`'s `YAML_*` kind, or `0` for `-1` and any handle that is not a node. */
377
+ kind(n: i32): i32 {
378
+ const kinds: u8[] = this.kinds
379
+ return n >= 0 && n < this.top && n < toI32(kinds.length) ? toI32(kinds[n]) : 0
380
+ }
381
+
382
+ /** Node `n`'s style: `YAML_PLAIN`, a quoted or block-scalar style, `YAML_FLOW` or `YAML_BLOCK`. */
383
+ style(n: i32): i32 {
384
+ const styles: u8[] = this.styles
385
+ return n >= 0 && n < this.top && n < toI32(styles.length) ? toI32(styles[n]) : 0
386
+ }
387
+
388
+ /** The first byte of node `n`'s source, its properties not included, or `-1`. */
389
+ start(n: i32): i32 {
390
+ return n < this.top ? yamlAt(this.starts, n) : -1
391
+ }
392
+
393
+ /** One past the last byte of node `n`'s source, or `-1`. */
394
+ end(n: i32): i32 {
395
+ return n < this.top ? yamlAt(this.ends, n) : -1
396
+ }
397
+
398
+ /** How many children a map (its pairs) or a seq (its items) has; `0` for any other node. */
399
+ count(n: i32): i32 {
400
+ const k: i32 = this.kind(n)
401
+ return k === YAML_MAP || k === YAML_SEQ ? yamlAt(this.counts, n) : 0
402
+ }
403
+
404
+ /** Child `i` of a map or a seq, or `-1` past its last. */
405
+ child(n: i32, i: i32): i32 {
406
+ if (i < 0 || i >= this.count(n)) {
407
+ return -1
408
+ }
409
+ return yamlAt(this.kids, yamlAt(this.firsts, n) + i)
410
+ }
411
+
412
+ /** A pair's key node, or `-1` for a node that is not a pair. */
413
+ key(pair: i32): i32 {
414
+ return this.kind(pair) === YAML_PAIR ? yamlAt(this.firsts, pair) : -1
415
+ }
416
+
417
+ /** A pair's value node, or `-1` for an empty value (`a:`, which reads as null) and any node that is not a pair. */
418
+ value(pair: i32): i32 {
419
+ return this.kind(pair) === YAML_PAIR ? yamlAt(this.counts, pair) : -1
420
+ }
421
+
422
+ /** The node an alias names, or `-1` for an alias with no anchor and any node that is not an alias. */
423
+ target(n: i32): i32 {
424
+ return this.kind(n) === YAML_ALIAS ? yamlAt(this.firsts, n) : -1
425
+ }
426
+
427
+ /** Node `n` itself, or the node it names when it is an alias. */
428
+ resolved(n: i32): i32 {
429
+ return this.kind(n) === YAML_ALIAS ? yamlAt(this.firsts, n) : n
430
+ }
431
+
432
+ /** Node `n`'s anchor name, without its `&`, or `""`. */
433
+ anchor(n: i32): string {
434
+ const slot: i32 = n < this.top ? yamlAt(this.props, n) : -1
435
+ if (slot < 0) {
436
+ return ""
437
+ }
438
+ const from: i32 = yamlAt(this.propAnchorStarts, slot)
439
+ return from < 0 ? "" : this.source.substring(from, yamlAt(this.propAnchorEnds, slot))
440
+ }
441
+
442
+ /**
443
+ * Node `n`'s tag, resolved: `"int"` for `!!int` (the `tag:yaml.org,2002:`
444
+ * prefix stripped), `"!local"` for `!local`, `"!"` for the non-specific `!`,
445
+ * a `%TAG` handle's prefix joined to its suffix, or `""` for an untagged node.
446
+ */
447
+ tag(n: i32): string {
448
+ const slot: i32 = n < this.top ? yamlAt(this.props, n) : -1
449
+ const tags: string[] = this.propTags
450
+ return slot >= 0 && slot < toI32(tags.length) ? tags[slot] : ""
451
+ }
452
+
453
+ /**
454
+ * A scalar's text, decoded: quotes and escapes undone, lines folded, a block
455
+ * scalar's indentation and chomping applied. An alias answers its target's
456
+ * text, and any other node `""`. A `\u` surrogate pair is one character; a
457
+ * lone surrogate escape is kept as its three bytes, which are not UTF-8.
458
+ * Decoded on every call; keep the result.
459
+ */
460
+ text(n: i32): string {
461
+ const at: i32 = this.resolved(n)
462
+ if (this.kind(at) !== YAML_SCALAR) {
463
+ return ""
464
+ }
465
+ return yamlDecode(this, at)
466
+ }
467
+
468
+ /**
469
+ * A scalar's core-schema type: `YAML_NULL`, `YAML_BOOL`, `YAML_INT`,
470
+ * `YAML_FLOAT` or `YAML_STRING`; through an alias; `-1` for a collection.
471
+ * A quoted or block scalar is a string, and so is any tagged one the core
472
+ * schema does not describe; a `!!str` plain scalar is a string too.
473
+ */
474
+ type(n: i32): i32 {
475
+ const at: i32 = this.resolved(n)
476
+ if (this.kind(at) !== YAML_SCALAR) {
477
+ return -1
478
+ }
479
+ const types: u8[] = this.types
480
+ const have: i32 = at >= 0 && at < toI32(types.length) ? toI32(types[at]) : YAML_STRING
481
+ if (have !== YAML_UNRESOLVED) {
482
+ return have
483
+ }
484
+ const resolvedType: i32 = yamlResolve(this, at)
485
+ if (at >= 0 && at < toI32(types.length)) {
486
+ types[at] = toU8(resolvedType)
487
+ }
488
+ return resolvedType
489
+ }
490
+
491
+ /** Whether a scalar is the boolean `true`; `false` for anything that is not a `YAML_BOOL`. */
492
+ boolValue(n: i32): boolean {
493
+ if (this.type(n) !== YAML_BOOL) {
494
+ return false
495
+ }
496
+ const at: i32 = this.resolved(n)
497
+ const code: i32 =
498
+ this.style(at) === YAML_PLAIN
499
+ ? yamlByte(this.source, this.start(at))
500
+ : yamlByte(yamlDecode(this, at), 0)
501
+ return code === 116 || code === 84
502
+ }
503
+
504
+ /** A `YAML_INT` scalar's value; `0` for anything else, and the value wrapped past 64 bits. */
505
+ intValue(n: i32): i64 {
506
+ if (this.type(n) !== YAML_INT) {
507
+ return toI64(0)
508
+ }
509
+ const at: i32 = this.resolved(n)
510
+ if (this.style(at) === YAML_PLAIN) {
511
+ return yamlParseInt(this.source, this.start(at), this.end(at))
512
+ }
513
+ const decoded: string = yamlDecode(this, at)
514
+ return yamlParseInt(decoded, 0, toI32(decoded.length))
515
+ }
516
+
517
+ /** A `YAML_FLOAT` or `YAML_INT` scalar's value as an `f64`; `0` for anything else. */
518
+ floatValue(n: i32): f64 {
519
+ const t: i32 = this.type(n)
520
+ const at: i32 = this.resolved(n)
521
+ if (t === YAML_INT) {
522
+ return toF64(this.intValue(n))
523
+ }
524
+ if (t !== YAML_FLOAT) {
525
+ return toF64(0)
526
+ }
527
+ if (this.style(at) === YAML_PLAIN) {
528
+ return yamlParseFloat(this.source, this.start(at), this.end(at))
529
+ }
530
+ const decoded: string = yamlDecode(this, at)
531
+ return yamlParseFloat(decoded, 0, toI32(decoded.length))
532
+ }
533
+
534
+ // ---- Diagnostics -------------------------------------------------------------
535
+
536
+ /** How many diagnostics the parse found, errors and warnings, in order of where they start. */
537
+ diagnosticCount(): i32 {
538
+ return toI32(this.diagCodes.length)
539
+ }
540
+
541
+ /** How many of the diagnostics are errors: `0` for a valid stream. */
542
+ errorCount(): i32 {
543
+ return this.errors
544
+ }
545
+
546
+ /** How many of the diagnostics are warnings. */
547
+ warningCount(): i32 {
548
+ return this.warnings
549
+ }
550
+
551
+ /** Diagnostic `i`'s `YAML_ERR_*` code, or `0`. */
552
+ diagnosticCode(i: i32): i32 {
553
+ const code: i32 = i < 0 ? 0 : yamlAt(this.diagCodes, i)
554
+ return code < 0 ? 0 : code
555
+ }
556
+
557
+ /** Whether diagnostic `i` is a warning rather than an error. */
558
+ diagnosticIsWarning(i: i32): boolean {
559
+ const warnings: boolean[] = this.diagWarnings
560
+ return i >= 0 && i < toI32(warnings.length) ? warnings[i] : false
561
+ }
562
+
563
+ /** The first byte diagnostic `i` is about, or `-1`. */
564
+ diagnosticStart(i: i32): i32 {
565
+ return yamlAt(this.diagStarts, i)
566
+ }
567
+
568
+ /** One past the last byte diagnostic `i` is about, or `-1`; the same as its start for a point. */
569
+ diagnosticEnd(i: i32): i32 {
570
+ return yamlAt(this.diagEnds, i)
571
+ }
572
+
573
+ /** Diagnostic `i`'s message, such as `Map key "a" is duplicated` (a capital first, as other YAML tooling spells them), or `""`. */
574
+ diagnosticMessage(i: i32): string {
575
+ const messages: string[] = this.diagMessages
576
+ return i >= 0 && i < toI32(messages.length) ? messages[i] : ""
577
+ }
578
+
579
+ /** The document diagnostic `i` was found in, or `-1`. */
580
+ diagnosticDocument(i: i32): i32 {
581
+ return yamlAt(this.diagDocuments, i)
582
+ }
583
+
584
+ // ---- Comments ----------------------------------------------------------------
585
+
586
+ /** How many comments the stream kept: none unless `keepComments`. */
587
+ commentCount(): i32 {
588
+ return toI32(this.commentStarts.length)
589
+ }
590
+
591
+ /** Comment `i`'s first byte, its `#`, or `-1`. */
592
+ commentStart(i: i32): i32 {
593
+ return yamlAt(this.commentStarts, i)
594
+ }
595
+
596
+ /** One past comment `i`'s last byte, before its line break, or `-1`. */
597
+ commentEnd(i: i32): i32 {
598
+ return yamlAt(this.commentEnds, i)
599
+ }
600
+
601
+ /** Comment `i`'s text after its `#`, or `""`. */
602
+ comment(i: i32): string {
603
+ const from: i32 = this.commentStart(i)
604
+ return from < 0 ? "" : this.source.substring(from + 1, this.commentEnd(i))
605
+ }
606
+
607
+ // ---- Building ----------------------------------------------------------------
608
+
609
+ /** Internal: a new node of `kind` and `style` over `[start, end)`, its other fields empty. */
610
+ newNode(kind: i32, style: i32, start: i32, end: i32): i32 {
611
+ if (this.top >= this.cap) {
612
+ this.grow()
613
+ }
614
+ const n: i32 = this.top
615
+ this.top = n + 1
616
+ yamlPut(this.starts, n, start)
617
+ yamlPut(this.ends, n, end)
618
+ yamlPut(this.firsts, n, -1)
619
+ yamlPut(this.counts, n, 0)
620
+ yamlPut(this.extras, n, 0)
621
+ yamlPut(this.props, n, -1)
622
+ yamlPutU8(this.kinds, n, kind)
623
+ yamlPutU8(this.styles, n, style)
624
+ yamlPutU8(this.types, n, YAML_UNRESOLVED)
625
+ return n
626
+ }
627
+
628
+ /** Internal: doubles every node array together. */
629
+ grow(): void {
630
+ const room: i32 = this.cap * 2
631
+ this.starts = yamlRoomI32(this.starts, room)
632
+ this.ends = yamlRoomI32(this.ends, room)
633
+ this.firsts = yamlRoomI32(this.firsts, room)
634
+ this.counts = yamlRoomI32(this.counts, room)
635
+ this.extras = yamlRoomI32(this.extras, room)
636
+ this.props = yamlRoomI32(this.props, room)
637
+ this.kinds = yamlRoomU8(this.kinds, room)
638
+ this.styles = yamlRoomU8(this.styles, room)
639
+ this.types = yamlRoomU8(this.types, room)
640
+ this.cap = room
641
+ }
642
+
643
+ /** Internal: appends `n` to `kids`, growing it by doubling; answers its slot. */
644
+ pushKid(n: i32): i32 {
645
+ const slot: i32 = this.kidTop
646
+ this.kids = yamlRoomI32(this.kids, slot + 1)
647
+ yamlPut(this.kids, slot, n)
648
+ this.kidTop = slot + 1
649
+ return slot
650
+ }
651
+
652
+ /** Internal: records a diagnostic. */
653
+ report(code: i32, warning: boolean, message: string, start: i32, end: i32, document: i32): void {
654
+ this.diagCodes.push(code)
655
+ this.diagWarnings.push(warning)
656
+ this.diagMessages.push(message)
657
+ this.diagStarts.push(start)
658
+ this.diagEnds.push(end < start ? start : end)
659
+ this.diagDocuments.push(document)
660
+ if (warning) {
661
+ this.warnings += 1
662
+ } else {
663
+ this.errors += 1
664
+ }
665
+ }
666
+
667
+ /** Internal: a new, empty property slot. */
668
+ newProps(): i32 {
669
+ const slot: i32 = toI32(this.propTags.length)
670
+ this.propAnchorStarts.push(YAML_NONE)
671
+ this.propAnchorEnds.push(YAML_NONE)
672
+ this.propTagStarts.push(YAML_NONE)
673
+ this.propTagEnds.push(YAML_NONE)
674
+ this.propStarts.push(YAML_NONE)
675
+ this.propEnds.push(YAML_NONE)
676
+ this.propEntries.push(YAML_NONE)
677
+ this.propTags.push("")
678
+ return slot
679
+ }
680
+
681
+ /** Internal: sorts the diagnostics by start, keeping the order of those that start together. */
682
+ sortDiagnostics(): void {
683
+ const count: i32 = toI32(this.diagCodes.length)
684
+ let i: i32 = 1
685
+ while (i < count) {
686
+ const at: i32 = yamlAt(this.diagStarts, i)
687
+ if (at < yamlAt(this.diagStarts, i - 1)) {
688
+ // Insertion: diagnostics arrive nearly sorted, so each moves a step or two.
689
+ const code: i32 = yamlAt(this.diagCodes, i)
690
+ const end: i32 = yamlAt(this.diagEnds, i)
691
+ const document: i32 = yamlAt(this.diagDocuments, i)
692
+ const message: string = this.diagnosticMessage(i)
693
+ const warning: boolean = this.diagnosticIsWarning(i)
694
+ let j: i32 = i
695
+ while (j > 0 && yamlAt(this.diagStarts, j - 1) > at) {
696
+ this.moveDiagnostic(j - 1, j)
697
+ j -= 1
698
+ }
699
+ yamlPut(this.diagCodes, j, code)
700
+ yamlPut(this.diagStarts, j, at)
701
+ yamlPut(this.diagEnds, j, end)
702
+ yamlPut(this.diagDocuments, j, document)
703
+ const messages: string[] = this.diagMessages
704
+ if (j >= 0 && j < toI32(messages.length)) {
705
+ messages[j] = message
706
+ }
707
+ const warnings: boolean[] = this.diagWarnings
708
+ if (j >= 0 && j < toI32(warnings.length)) {
709
+ warnings[j] = warning
710
+ }
711
+ }
712
+ i += 1
713
+ }
714
+ }
715
+
716
+ /** Internal: copies diagnostic `from` over diagnostic `to`. */
717
+ moveDiagnostic(from: i32, to: i32): void {
718
+ yamlPut(this.diagCodes, to, yamlAt(this.diagCodes, from))
719
+ yamlPut(this.diagStarts, to, yamlAt(this.diagStarts, from))
720
+ yamlPut(this.diagEnds, to, yamlAt(this.diagEnds, from))
721
+ yamlPut(this.diagDocuments, to, yamlAt(this.diagDocuments, from))
722
+ const messages: string[] = this.diagMessages
723
+ if (to >= 0 && to < toI32(messages.length)) {
724
+ messages[to] = this.diagnosticMessage(from)
725
+ }
726
+ const warnings: boolean[] = this.diagWarnings
727
+ if (to >= 0 && to < toI32(warnings.length)) {
728
+ warnings[to] = this.diagnosticIsWarning(from)
729
+ }
730
+ }
731
+ }
732
+
733
+ // ---- Array helpers ---------------------------------------------------------------
734
+
735
+ /** `a[i]`, or `-1` outside it. */
736
+ const yamlAt = (a: i32[], i: i32): i32 => (i >= 0 && i < toI32(a.length) ? a[i] : -1)
737
+
738
+ /** `a[i] = v`, when `i` is inside `a`. */
739
+ const yamlPut = (a: i32[], i: i32, v: i32): void => {
740
+ if (i >= 0 && i < toI32(a.length)) {
741
+ a[i] = v
742
+ }
743
+ }
744
+
745
+ /** `a[i] = v` as a byte, when `i` is inside `a`. */
746
+ const yamlPutU8 = (a: u8[], i: i32, v: i32): void => {
747
+ if (i >= 0 && i < toI32(a.length)) {
748
+ a[i] = toU8(v)
749
+ }
750
+ }
751
+
752
+ /** `a`, or a copy of it at least `n` long and at least twice as long. */
753
+ const yamlRoomI32 = (a: i32[], n: i32): i32[] => {
754
+ const have: i32 = toI32(a.length)
755
+ if (n <= have) {
756
+ return a
757
+ }
758
+ const grown: i32[] = new Array<i32>(n > have * 2 ? n : have * 2)
759
+ grown.set(a, 0)
760
+ return grown
761
+ }
762
+
763
+ /** `a`, or a copy of it at least `n` long and at least twice as long. */
764
+ const yamlRoomU8 = (a: u8[], n: i32): u8[] => {
765
+ const have: i32 = toI32(a.length)
766
+ if (n <= have) {
767
+ return a
768
+ }
769
+ const grown: u8[] = new Array<u8>(n > have * 2 ? n : have * 2)
770
+ grown.set(a, 0)
771
+ return grown
772
+ }
773
+
774
+ /** The byte of `text` at `i`, or `-1` outside it. */
775
+ const yamlByte = (text: string, i: i32): i32 =>
776
+ i >= 0 && i < toI32(text.length) ? toI32(text.charCodeAt(i)) : -1
777
+
778
+ /** Whether `text[a0, a1)` and `text[b0, b1)` hold the same bytes. */
779
+ const yamlSameBytes = (text: string, a0: i32, a1: i32, b0: i32, b1: i32): boolean => {
780
+ if (a1 - a0 !== b1 - b0) {
781
+ return false
782
+ }
783
+ let i: i32 = 0
784
+ while (i < a1 - a0) {
785
+ if (yamlByte(text, a0 + i) !== yamlByte(text, b0 + i)) {
786
+ return false
787
+ }
788
+ i += 1
789
+ }
790
+ return true
791
+ }
792
+
793
+ /** Whether `text[from, to)` is exactly `word`. */
794
+ const yamlIsWord = (text: string, from: i32, to: i32, word: string): boolean => {
795
+ const n: i32 = toI32(word.length)
796
+ if (to - from !== n) {
797
+ return false
798
+ }
799
+ let i: i32 = 0
800
+ while (i < n) {
801
+ if (yamlByte(text, from + i) !== toI32(word.charCodeAt(i))) {
802
+ return false
803
+ }
804
+ i += 1
805
+ }
806
+ return true
807
+ }
808
+
809
+ const yamlIsBreak = (code: i32): boolean => code === YAML_LF || code === YAML_CR
810
+ const yamlIsBlank = (code: i32): boolean => code === YAML_SPACE || code === YAML_TAB
811
+ /** A blank, a break or the end of the text: what may follow an indicator such as `- `. */
812
+ const yamlIsWs = (code: i32): boolean =>
813
+ code < 0 || code === YAML_SPACE || code === YAML_TAB || code === YAML_LF || code === YAML_CR
814
+ const yamlIsFlowIndicator = (code: i32): boolean =>
815
+ code === YAML_COMMA ||
816
+ code === YAML_LBRACKET ||
817
+ code === YAML_RBRACKET ||
818
+ code === YAML_LBRACE ||
819
+ code === YAML_RBRACE
820
+ const yamlIsDigit = (code: i32): boolean => code >= YAML_ZERO && code <= YAML_NINE
821
+ const yamlHexValue = (code: i32): i32 => {
822
+ if (code >= YAML_ZERO && code <= YAML_NINE) {
823
+ return code - YAML_ZERO
824
+ }
825
+ if (code >= 97 && code <= 102) {
826
+ return code - 87
827
+ }
828
+ if (code >= 65 && code <= 70) {
829
+ return code - 55
830
+ }
831
+ return -1
832
+ }
833
+
834
+ /** Past `text[i]`, a break: one byte, or two for CR LF. */
835
+ const yamlAfterBreak = (text: string, i: i32): i32 =>
836
+ yamlByte(text, i) === YAML_CR && yamlByte(text, i + 1) === YAML_LF ? i + 2 : i + 1
837
+
838
+ /**
839
+ * The first break in `text[from, to)`, or `to`. Short of the end of the text
840
+ * the search reads only `[from, to)`, so a caller testing a short range on a
841
+ * long line pays for the range, not the line.
842
+ */
843
+ const yamlLineEnd = (text: string, from: i32, to: i32): i32 => {
844
+ if (to < toI32(text.length)) {
845
+ let i: i32 = from
846
+ while (i < to && !yamlIsBreak(yamlByte(text, i))) {
847
+ i += 1
848
+ }
849
+ return i
850
+ }
851
+ const at: i32 = indexOfAny(text, YAML_BREAKS, from)
852
+ return at < 0 || at > to ? to : at
853
+ }
854
+
855
+ // ---- Decoding ------------------------------------------------------------------
856
+
857
+ /** The bytes a scalar decodes to, collected before they become one string. */
858
+ class YamlBuffer {
859
+ bytes: u8[]
860
+ count: i32 = 0
861
+
862
+ constructor(room: i32) {
863
+ const least: i32 = 16
864
+ this.bytes = new Array<u8>(room > least ? room : least)
865
+ }
866
+
867
+ /** Internal: appends one byte. */
868
+ push(code: i32): void {
869
+ if (this.count >= toI32(this.bytes.length)) {
870
+ this.bytes = yamlRoomU8(this.bytes, this.count + 1)
871
+ }
872
+ yamlPutU8(this.bytes, this.count, code)
873
+ this.count += 1
874
+ }
875
+
876
+ /** Internal: appends `count` copies of `code`. */
877
+ repeat(code: i32, count: i32): void {
878
+ let i: i32 = 0
879
+ while (i < count) {
880
+ this.push(code)
881
+ i += 1
882
+ }
883
+ }
884
+
885
+ /** Internal: appends `text[from, to)`. */
886
+ copy(text: string, from: i32, to: i32): void {
887
+ let i: i32 = from
888
+ while (i < to) {
889
+ this.push(yamlByte(text, i))
890
+ i += 1
891
+ }
892
+ }
893
+
894
+ /** Internal: appends code point `cp` as UTF-8. */
895
+ codePoint(cp: i32): void {
896
+ const six: i32 = 6
897
+ const low: i32 = 63
898
+ const continuation: i32 = 0x80
899
+ if (cp < continuation) {
900
+ this.push(cp)
901
+ return
902
+ }
903
+ const two: i32 = 0x800
904
+ const three: i32 = 0x10000
905
+ if (cp < two) {
906
+ const lead: i32 = 0xc0
907
+ this.push(lead + (cp >> six))
908
+ } else if (cp < three) {
909
+ const lead: i32 = 0xe0
910
+ this.push(lead + (cp >> (six * 2)))
911
+ this.push(continuation + ((cp >> six) & low))
912
+ } else {
913
+ const lead: i32 = 0xf0
914
+ this.push(lead + (cp >> (six * 3)))
915
+ this.push(continuation + ((cp >> (six * 2)) & low))
916
+ this.push(continuation + ((cp >> six) & low))
917
+ }
918
+ this.push(continuation + (cp & low))
919
+ }
920
+
921
+ /** Internal: drops trailing spaces and tabs, but none at or below `floor`. */
922
+ trimBlanks(floor: i32): void {
923
+ const bytes: u8[] = this.bytes
924
+ while (this.count > floor) {
925
+ const at: i32 = this.count - 1
926
+ const code: i32 = at >= 0 && at < toI32(bytes.length) ? toI32(bytes[at]) : 0
927
+ if (!yamlIsBlank(code)) {
928
+ return
929
+ }
930
+ this.count = at
931
+ }
932
+ }
933
+
934
+ /** Internal: the bytes as a string. */
935
+ text(): string {
936
+ return String.fromBytes(this.bytes, 0, this.count)
937
+ }
938
+ }
939
+
940
+ /** Internal: scalar `n`'s text, decoded from its body. */
941
+ const yamlDecode = (stream: YamlStream, n: i32): string => {
942
+ const style: i32 = stream.style(n)
943
+ const from: i32 = yamlAt(stream.firsts, n)
944
+ const to: i32 = yamlAt(stream.counts, n)
945
+ if (from < 0 || to <= from) {
946
+ return ""
947
+ }
948
+ if (style === YAML_LITERAL || style === YAML_FOLDED) {
949
+ return yamlDecodeBlock(stream.source, from, to, yamlAt(stream.extras, n), style === YAML_FOLDED)
950
+ }
951
+ return yamlDecodeFlow(stream.source, from, to, style)
952
+ }
953
+
954
+ /**
955
+ * A plain or quoted scalar's body decoded: each break with the blanks around it
956
+ * folded to a space, or to one newline per empty line it holds; `''` undone in
957
+ * a single-quoted body, escapes in a double-quoted one.
958
+ */
959
+ const yamlDecodeFlow = (text: string, from: i32, to: i32, style: i32): string => {
960
+ // Most scalars hold nothing to undo: their text is their body.
961
+ let i: i32 = from
962
+ while (i < to) {
963
+ const code: i32 = yamlByte(text, i)
964
+ if (
965
+ yamlIsBreak(code) ||
966
+ (style === YAML_SINGLE_QUOTED && code === YAML_SQUOTE) ||
967
+ (style === YAML_DOUBLE_QUOTED && code === YAML_BACKSLASH)
968
+ ) {
969
+ break
970
+ }
971
+ i += 1
972
+ }
973
+ if (i >= to) {
974
+ return text.substring(from, to)
975
+ }
976
+ const out: YamlBuffer = new YamlBuffer(to - from)
977
+ out.copy(text, from, i)
978
+ // Blanks the source spelled as escapes are content, and a fold never trims them.
979
+ let floor: i32 = 0
980
+ while (i < to) {
981
+ const code: i32 = yamlByte(text, i)
982
+ if (yamlIsBreak(code)) {
983
+ out.trimBlanks(floor)
984
+ i = yamlAfterBreak(text, i)
985
+ let empties: i32 = 0
986
+ while (true) {
987
+ let j: i32 = i
988
+ while (j < to && yamlIsBlank(yamlByte(text, j))) {
989
+ j += 1
990
+ }
991
+ if (j < to && yamlIsBreak(yamlByte(text, j))) {
992
+ empties += 1
993
+ i = yamlAfterBreak(text, j)
994
+ } else {
995
+ i = j
996
+ break
997
+ }
998
+ }
999
+ if (empties === 0) {
1000
+ out.push(YAML_SPACE)
1001
+ } else {
1002
+ out.repeat(YAML_LF, empties)
1003
+ }
1004
+ floor = out.count
1005
+ } else if (style === YAML_SINGLE_QUOTED && code === YAML_SQUOTE) {
1006
+ out.push(YAML_SQUOTE)
1007
+ i += yamlByte(text, i + 1) === YAML_SQUOTE ? 2 : 1
1008
+ } else if (style === YAML_DOUBLE_QUOTED && code === YAML_BACKSLASH) {
1009
+ if (i + 1 < to && yamlIsBreak(yamlByte(text, i + 1))) {
1010
+ // An escaped break joins the lines; the empty lines after it stay.
1011
+ i = yamlAfterBreak(text, i + 1)
1012
+ while (true) {
1013
+ let j: i32 = i
1014
+ while (j < to && yamlIsBlank(yamlByte(text, j))) {
1015
+ j += 1
1016
+ }
1017
+ if (j < to && yamlIsBreak(yamlByte(text, j))) {
1018
+ out.push(YAML_LF)
1019
+ i = yamlAfterBreak(text, j)
1020
+ } else {
1021
+ i = j
1022
+ break
1023
+ }
1024
+ }
1025
+ } else {
1026
+ i = yamlDecodeEscape(text, i, to, out)
1027
+ }
1028
+ floor = out.count
1029
+ } else {
1030
+ out.push(code)
1031
+ i += 1
1032
+ }
1033
+ }
1034
+ return out.text()
1035
+ }
1036
+
1037
+ /** The low surrogate a `\u` escape at `text[at]` writes, or `-1` when it writes none before `to`. */
1038
+ const yamlLowSurrogateAt = (text: string, at: i32, to: i32): i32 => {
1039
+ if (at + 6 > to || yamlByte(text, at) !== YAML_BACKSLASH || yamlByte(text, at + 1) !== 117) {
1040
+ return -1
1041
+ }
1042
+ let cp: i32 = 0
1043
+ let k: i32 = 0
1044
+ while (k < 4) {
1045
+ const v: i32 = yamlHexValue(yamlByte(text, at + 2 + k))
1046
+ if (v < 0) {
1047
+ return -1
1048
+ }
1049
+ cp = cp * 16 + v
1050
+ k += 1
1051
+ }
1052
+ return cp >= 0xdc00 && cp < 0xe000 ? cp : -1
1053
+ }
1054
+
1055
+ /** Appends what the escape at `text[at]`, a `\`, stands for; answers the byte past it. */
1056
+ const yamlDecodeEscape = (text: string, at: i32, to: i32, out: YamlBuffer): i32 => {
1057
+ if (at + 1 >= to) {
1058
+ out.push(YAML_BACKSLASH)
1059
+ return at + 1
1060
+ }
1061
+ const e: i32 = yamlByte(text, at + 1)
1062
+ let digits: i32 = 0
1063
+ if (e === 120) {
1064
+ digits = 2
1065
+ } else if (e === 117) {
1066
+ digits = 4
1067
+ } else if (e === 85) {
1068
+ digits = 8
1069
+ }
1070
+ if (digits > 0) {
1071
+ let cp: i32 = 0
1072
+ let k: i32 = 0
1073
+ while (k < digits && at + 2 + k < to) {
1074
+ const v: i32 = yamlHexValue(yamlByte(text, at + 2 + k))
1075
+ if (v < 0 || cp > 0x10ffff) {
1076
+ break
1077
+ }
1078
+ cp = cp * 16 + v
1079
+ k += 1
1080
+ }
1081
+ if (k === digits && cp <= 0x10ffff) {
1082
+ // A `\u` high surrogate and a `\u` low one after it are one code point, written as one.
1083
+ const low: i32 = digits === 4 && cp >= 0xd800 && cp < 0xdc00 ? yamlLowSurrogateAt(text, at + 6, to) : -1
1084
+ if (low >= 0) {
1085
+ out.codePoint((cp - 0xd800) * 0x400 + (low - 0xdc00) + 0x10000)
1086
+ return at + 12
1087
+ }
1088
+ out.codePoint(cp)
1089
+ return at + 2 + digits
1090
+ }
1091
+ out.push(YAML_BACKSLASH)
1092
+ out.push(e)
1093
+ return at + 2
1094
+ }
1095
+ const mapped: i32 = yamlEscapeValue(e)
1096
+ if (mapped < 0) {
1097
+ out.push(YAML_BACKSLASH)
1098
+ out.push(e)
1099
+ } else {
1100
+ out.codePoint(mapped)
1101
+ }
1102
+ return at + 2
1103
+ }
1104
+
1105
+ /** The code point `\` and `e` stand for in a double-quoted scalar, or `-1` for no escape. */
1106
+ const yamlEscapeValue = (e: i32): i32 => {
1107
+ if (e === YAML_ZERO) {
1108
+ return 0
1109
+ }
1110
+ if (e === 97) {
1111
+ return 7
1112
+ }
1113
+ if (e === 98) {
1114
+ return 8
1115
+ }
1116
+ if (e === 116 || e === YAML_TAB) {
1117
+ return 9
1118
+ }
1119
+ if (e === 110) {
1120
+ return 10
1121
+ }
1122
+ if (e === 118) {
1123
+ return 11
1124
+ }
1125
+ if (e === 102) {
1126
+ return 12
1127
+ }
1128
+ if (e === 114) {
1129
+ return 13
1130
+ }
1131
+ if (e === 101) {
1132
+ return 27
1133
+ }
1134
+ if (e === YAML_SPACE || e === YAML_DQUOTE || e === 47 || e === YAML_BACKSLASH) {
1135
+ return e
1136
+ }
1137
+ if (e === 78) {
1138
+ return 0x85
1139
+ }
1140
+ if (e === 95) {
1141
+ return 0xa0
1142
+ }
1143
+ if (e === 76) {
1144
+ return 0x2028
1145
+ }
1146
+ if (e === 80) {
1147
+ return 0x2029
1148
+ }
1149
+ return -1
1150
+ }
1151
+
1152
+ /**
1153
+ * A block scalar's lines decoded: the content indent dropped from each, a
1154
+ * folded scalar's lines joined, and the trailing breaks chomped.
1155
+ */
1156
+ const yamlDecodeBlock = (text: string, from: i32, to: i32, extras: i32, folded: boolean): string => {
1157
+ const indent: i32 = (extras >> 3) - 1
1158
+ const explicit: boolean = (extras & 4) !== 0
1159
+ const chomp: i32 = extras & 3
1160
+ const out: YamlBuffer = new YamlBuffer(to - from)
1161
+ let i: i32 = from
1162
+ let seenContent: boolean = false
1163
+ let previousMore: boolean = false
1164
+ // Breaks since the last content line's text, its own included.
1165
+ let pending: i32 = 0
1166
+ while (i < to) {
1167
+ let j: i32 = i
1168
+ while (j < to && j - i < indent && yamlByte(text, j) === YAML_SPACE) {
1169
+ j += 1
1170
+ }
1171
+ const e: i32 = yamlLineEnd(text, j, to)
1172
+ const hasBreak: boolean = e < to
1173
+ let empty: boolean = j === e
1174
+ if (!empty && (j - i < indent || (!seenContent && !explicit))) {
1175
+ // Short of the indent, or before the indent was found: empty when only
1176
+ // spaces, as the scanner counts it; a tab makes the first content line.
1177
+ let k: i32 = j
1178
+ while (k < e && yamlByte(text, k) === YAML_SPACE) {
1179
+ k += 1
1180
+ }
1181
+ empty = k === e
1182
+ }
1183
+ if (empty) {
1184
+ if (hasBreak) {
1185
+ pending += 1
1186
+ }
1187
+ } else {
1188
+ const more: boolean = yamlIsBlank(yamlByte(text, j))
1189
+ if (!seenContent) {
1190
+ out.repeat(YAML_LF, pending)
1191
+ } else if (folded && !more && !previousMore) {
1192
+ if (pending === 1) {
1193
+ out.push(YAML_SPACE)
1194
+ } else {
1195
+ out.repeat(YAML_LF, pending - 1)
1196
+ }
1197
+ } else {
1198
+ out.repeat(YAML_LF, pending)
1199
+ }
1200
+ out.copy(text, j, e)
1201
+ seenContent = true
1202
+ previousMore = more
1203
+ pending = hasBreak ? 1 : 0
1204
+ }
1205
+ i = hasBreak ? yamlAfterBreak(text, e) : to
1206
+ }
1207
+ if (seenContent && pending === 0) {
1208
+ // The last line ran to the end of the text: its break is implied.
1209
+ pending = 1
1210
+ }
1211
+ if (chomp === YAML_KEEP) {
1212
+ out.repeat(YAML_LF, pending)
1213
+ } else if (chomp === YAML_CLIP && seenContent && pending > 0) {
1214
+ out.push(YAML_LF)
1215
+ }
1216
+ return out.text()
1217
+ }
1218
+
1219
+ // ---- The core schema -------------------------------------------------------------
1220
+
1221
+ /** Internal: scalar `n`'s core-schema type, from its text and its tag. */
1222
+ const yamlResolve = (stream: YamlStream, n: i32): i32 => {
1223
+ const tag: string = stream.tag(n)
1224
+ const plain: boolean = stream.style(n) === YAML_PLAIN
1225
+ if (tag === "") {
1226
+ return plain ? yamlPlainType(stream.source, stream.start(n), stream.end(n)) : YAML_STRING
1227
+ }
1228
+ if (tag !== "null" && tag !== "bool" && tag !== "int" && tag !== "float") {
1229
+ return YAML_STRING
1230
+ }
1231
+ let found: i32 = YAML_STRING
1232
+ if (plain) {
1233
+ found = yamlPlainType(stream.source, stream.start(n), stream.end(n))
1234
+ } else {
1235
+ const decoded: string = yamlDecode(stream, n)
1236
+ found = yamlPlainType(decoded, 0, toI32(decoded.length))
1237
+ }
1238
+ if (tag === "null") {
1239
+ return found === YAML_NULL ? YAML_NULL : YAML_STRING
1240
+ }
1241
+ if (tag === "bool") {
1242
+ return found === YAML_BOOL ? YAML_BOOL : YAML_STRING
1243
+ }
1244
+ if (tag === "int") {
1245
+ return found === YAML_INT ? YAML_INT : YAML_STRING
1246
+ }
1247
+ return found === YAML_FLOAT || found === YAML_INT ? YAML_FLOAT : YAML_STRING
1248
+ }
1249
+
1250
+ /** The core-schema type of the untagged plain scalar `text[from, to)`. */
1251
+ const yamlPlainType = (text: string, from: i32, to: i32): i32 => {
1252
+ if (to <= from) {
1253
+ return YAML_NULL
1254
+ }
1255
+ const c: i32 = yamlByte(text, from)
1256
+ const letter: boolean = c === 110 || c === 78 || c === 116 || c === 84 || c === 102 || c === 70
1257
+ if (!letter && !yamlIsDigit(c) && c !== YAML_DOT && c !== YAML_DASH && c !== YAML_PLUS && c !== 126) {
1258
+ return YAML_STRING
1259
+ }
1260
+ if (
1261
+ yamlIsWord(text, from, to, "~") ||
1262
+ yamlIsWord(text, from, to, "null") ||
1263
+ yamlIsWord(text, from, to, "Null") ||
1264
+ yamlIsWord(text, from, to, "NULL")
1265
+ ) {
1266
+ return YAML_NULL
1267
+ }
1268
+ if (
1269
+ yamlIsWord(text, from, to, "true") ||
1270
+ yamlIsWord(text, from, to, "True") ||
1271
+ yamlIsWord(text, from, to, "TRUE") ||
1272
+ yamlIsWord(text, from, to, "false") ||
1273
+ yamlIsWord(text, from, to, "False") ||
1274
+ yamlIsWord(text, from, to, "FALSE")
1275
+ ) {
1276
+ return YAML_BOOL
1277
+ }
1278
+ if (letter) {
1279
+ return YAML_STRING
1280
+ }
1281
+ if (yamlIntBase(text, from, to) > 0) {
1282
+ return YAML_INT
1283
+ }
1284
+ if (yamlSpecialFloat(text, from, to) > 0 || yamlIsFloatForm(text, from, to)) {
1285
+ return YAML_FLOAT
1286
+ }
1287
+ return YAML_STRING
1288
+ }
1289
+
1290
+ /** The base of the core-schema integer `text[from, to)`, signed, decimal, `0x` or `0o`; `0` for none. */
1291
+ const yamlIntBase = (text: string, from: i32, to: i32): i32 => {
1292
+ let i: i32 = from
1293
+ const sign: i32 = yamlByte(text, i)
1294
+ if (sign === YAML_PLUS || sign === YAML_DASH) {
1295
+ i += 1
1296
+ }
1297
+ let base: i32 = 10
1298
+ const x: i32 = yamlByte(text, i + 1)
1299
+ if (yamlByte(text, i) === YAML_ZERO && (x === 120 || x === 111)) {
1300
+ base = x === 120 ? 16 : 8
1301
+ i += 2
1302
+ }
1303
+ if (i >= to) {
1304
+ return 0
1305
+ }
1306
+ while (i < to) {
1307
+ const v: i32 = yamlHexValue(yamlByte(text, i))
1308
+ if (v < 0 || v >= base) {
1309
+ return 0
1310
+ }
1311
+ i += 1
1312
+ }
1313
+ return base
1314
+ }
1315
+
1316
+ /** Whether `text[from, to)` is a core-schema float: `[-+]? (. digits | digits [. digits]) [e [-+] digits]`. */
1317
+ const yamlIsFloatForm = (text: string, from: i32, to: i32): boolean => {
1318
+ let i: i32 = from
1319
+ const sign: i32 = yamlByte(text, i)
1320
+ if (sign === YAML_PLUS || sign === YAML_DASH) {
1321
+ i += 1
1322
+ }
1323
+ const wholeStart: i32 = i
1324
+ while (i < to && yamlIsDigit(yamlByte(text, i))) {
1325
+ i += 1
1326
+ }
1327
+ const whole: i32 = i - wholeStart
1328
+ if (i < to && yamlByte(text, i) === YAML_DOT) {
1329
+ i += 1
1330
+ const fractionStart: i32 = i
1331
+ while (i < to && yamlIsDigit(yamlByte(text, i))) {
1332
+ i += 1
1333
+ }
1334
+ if (whole === 0 && i === fractionStart) {
1335
+ return false
1336
+ }
1337
+ } else if (whole === 0) {
1338
+ return false
1339
+ }
1340
+ if (i < to && (yamlByte(text, i) === 101 || yamlByte(text, i) === 69)) {
1341
+ i += 1
1342
+ const s: i32 = yamlByte(text, i)
1343
+ if (s === YAML_PLUS || s === YAML_DASH) {
1344
+ i += 1
1345
+ }
1346
+ const expStart: i32 = i
1347
+ while (i < to && yamlIsDigit(yamlByte(text, i))) {
1348
+ i += 1
1349
+ }
1350
+ if (i === expStart) {
1351
+ return false
1352
+ }
1353
+ }
1354
+ return i === to
1355
+ }
1356
+
1357
+ /** `1` for a core-schema positive infinity, `2` for a negative one, `3` for not-a-number, `0` otherwise. */
1358
+ const yamlSpecialFloat = (text: string, from: i32, to: i32): i32 => {
1359
+ const c: i32 = yamlByte(text, from)
1360
+ const at: i32 = c === YAML_PLUS || c === YAML_DASH ? from + 1 : from
1361
+ if (
1362
+ yamlIsWord(text, at, to, ".inf") ||
1363
+ yamlIsWord(text, at, to, ".Inf") ||
1364
+ yamlIsWord(text, at, to, ".INF")
1365
+ ) {
1366
+ return c === YAML_DASH ? 2 : 1
1367
+ }
1368
+ if (
1369
+ at === from &&
1370
+ (yamlIsWord(text, at, to, ".nan") || yamlIsWord(text, at, to, ".NaN") || yamlIsWord(text, at, to, ".NAN"))
1371
+ ) {
1372
+ return 3
1373
+ }
1374
+ return 0
1375
+ }
1376
+
1377
+ /** The integer `text[from, to)`, decimal, `0x` or `0o`, wrapped past 64 bits; `0` when it is not one. */
1378
+ const yamlParseInt = (text: string, from: i32, to: i32): i64 => {
1379
+ const base: i32 = yamlIntBase(text, from, to)
1380
+ let value: i64 = toI64(0)
1381
+ if (base === 0) {
1382
+ return value
1383
+ }
1384
+ let i: i32 = from
1385
+ const sign: i32 = yamlByte(text, i)
1386
+ if (sign === YAML_PLUS || sign === YAML_DASH) {
1387
+ i += 1
1388
+ }
1389
+ if (base !== 10) {
1390
+ i += 2
1391
+ }
1392
+ const wide: i64 = toI64(base)
1393
+ while (i < to) {
1394
+ value = wrappingAdd(wrappingMul(value, wide), toI64(yamlHexValue(yamlByte(text, i))))
1395
+ i += 1
1396
+ }
1397
+ return sign === YAML_DASH ? wrappingSub(toI64(0), value) : value
1398
+ }
1399
+
1400
+ /** The float `text[from, to)`: a core-schema float, infinity or not-a-number, or an integer widened. */
1401
+ const yamlParseFloat = (text: string, from: i32, to: i32): f64 => {
1402
+ const special: i32 = yamlSpecialFloat(text, from, to)
1403
+ if (special === 1) {
1404
+ return parseFloat("Infinity")
1405
+ }
1406
+ if (special === 2) {
1407
+ return parseFloat("-Infinity")
1408
+ }
1409
+ if (special === 3) {
1410
+ return parseFloat("")
1411
+ }
1412
+ if (yamlIntBase(text, from, to) > 0) {
1413
+ return toF64(yamlParseInt(text, from, to))
1414
+ }
1415
+ return yamlIsFloatForm(text, from, to) ? parseFloat(text.substring(from, to)) : toF64(0)
1416
+ }
1417
+
1418
+ // ---- The parser ------------------------------------------------------------------
1419
+
1420
+ /** `a[top] = v`, or a push when `a` is no longer than `top`; answers `top + 1`. */
1421
+ const yamlStackPush = (a: i32[], top: i32, v: i32): i32 => {
1422
+ if (top < toI32(a.length)) {
1423
+ yamlPut(a, top, v)
1424
+ } else {
1425
+ a.push(v)
1426
+ }
1427
+ return top + 1
1428
+ }
1429
+
1430
+ /** A tag with the core schema's `tag:yaml.org,2002:` prefix taken off. */
1431
+ const yamlShortenTag = (tag: string): string => {
1432
+ const prefix: i32 = toI32(YAML_CORE_PREFIX.length)
1433
+ if (toI32(tag.length) >= prefix && tag.substring(0, prefix) === YAML_CORE_PREFIX) {
1434
+ return tag.substring(prefix, toI32(tag.length))
1435
+ }
1436
+ return tag
1437
+ }
1438
+
1439
+ /** A byte a `%TAG` handle may hold between its `!`s: a letter, a digit or `-`. */
1440
+ const yamlIsHandleChar = (code: i32): boolean =>
1441
+ yamlIsDigit(code) || (code >= 65 && code <= 90) || (code >= 97 && code <= 122) || code === YAML_DASH
1442
+
1443
+ /** The bytes of a UTF-8 character that starts with `lead`. */
1444
+ const yamlCharWidth = (lead: i32): i32 => {
1445
+ if (lead >= 240) {
1446
+ return 4
1447
+ }
1448
+ if (lead >= 224) {
1449
+ return 3
1450
+ }
1451
+ return lead >= 192 ? 2 : 1
1452
+ }
1453
+
1454
+ /** Whether `\` followed by `code` is an escape YAML defines: `1`, or `2` for the hex ones. */
1455
+ const yamlEscapeKind = (code: i32): i32 => {
1456
+ if (code === 120 || code === 117 || code === 85) {
1457
+ return 2
1458
+ }
1459
+ if (
1460
+ code === YAML_ZERO ||
1461
+ code === 97 ||
1462
+ code === 98 ||
1463
+ code === 116 ||
1464
+ code === 110 ||
1465
+ code === 118 ||
1466
+ code === 102 ||
1467
+ code === 114 ||
1468
+ code === 101 ||
1469
+ code === YAML_SPACE ||
1470
+ code === YAML_DQUOTE ||
1471
+ code === 47 ||
1472
+ code === YAML_BACKSLASH ||
1473
+ code === 78 ||
1474
+ code === 95 ||
1475
+ code === 76 ||
1476
+ code === 80 ||
1477
+ code === YAML_TAB ||
1478
+ yamlIsBreak(code)
1479
+ ) {
1480
+ return 1
1481
+ }
1482
+ return 0
1483
+ }
1484
+
1485
+ /** One parse in progress: the cursor, the anchors in scope and the document's directives. */
1486
+ class YamlParser {
1487
+ stream: YamlStream
1488
+ text: string
1489
+ /** The anchors defined so far in this document: each name's range and its node, searched from the end. */
1490
+ anchorStarts: i32[]
1491
+ anchorEnds: i32[]
1492
+ anchorNodes: i32[]
1493
+ /** The anchors whose node is still being read, so an alias to one is recursive. */
1494
+ pendingStarts: i32[]
1495
+ pendingEnds: i32[]
1496
+ /** The `%TAG` handles this document declared, and their prefixes. */
1497
+ handleNames: string[]
1498
+ handlePrefixes: string[]
1499
+ /** The children of every collection still open, innermost last. */
1500
+ scratch: i32[]
1501
+ /** Hash sets for the duplicate check of maps past YAML_PAIRWISE_KEYS pairs, reused as maps nest and end. */
1502
+ keySets: YamlKeySet[]
1503
+ pos: i32 = 0
1504
+ len: i32 = 0
1505
+ depth: i32 = 0
1506
+ /** Whether `DEPTH_LIMIT` cut the text short, so the collections it left open are not reported. */
1507
+ depthHit: boolean = false
1508
+ maxDepth: i32 = 1000
1509
+ doc: i32 = 0
1510
+ /** The indent a flow collection's continuation lines must pass. */
1511
+ flowIndent: i32 = -1
1512
+ tabReportedAt: i32 = -1
1513
+ commentWatermark: i32 = 0
1514
+ anchorTop: i32 = 0
1515
+ pendingTop: i32 = 0
1516
+ scratchTop: i32 = 0
1517
+ keySetTop: i32 = 0
1518
+ /** The last error's range, so one mistake is not reported twice. */
1519
+ lastErrStart: i32 = -1
1520
+ lastErrEnd: i32 = -1
1521
+ /** What `peekLine` found: the next content line's indent and first byte. */
1522
+ lineIndent: i32 = 0
1523
+ lineContent: i32 = 0
1524
+ /** Where the current document's `---` is, or `-1` when it has none. */
1525
+ markerAt: i32 = -1
1526
+ flowIndentReported: boolean = false
1527
+ uniqueKeys: boolean = true
1528
+ keepComments: boolean = false
1529
+ firstOnly: boolean = false
1530
+ yamlDirective: boolean = false
1531
+ lineEof: boolean = false
1532
+
1533
+ constructor(text: string, options: YamlOptions) {
1534
+ this.stream = new YamlStream(text)
1535
+ this.text = text
1536
+ this.anchorStarts = []
1537
+ this.anchorEnds = []
1538
+ this.anchorNodes = []
1539
+ this.pendingStarts = []
1540
+ this.pendingEnds = []
1541
+ this.handleNames = []
1542
+ this.handlePrefixes = []
1543
+ this.scratch = []
1544
+ this.keySets = []
1545
+ this.len = toI32(text.length)
1546
+ this.maxDepth = options.maxDepth
1547
+ this.uniqueKeys = options.uniqueKeys
1548
+ this.stream.merge = options.merge
1549
+ this.keepComments = options.keepComments
1550
+ this.firstOnly = options.firstDocumentOnly
1551
+ }
1552
+
1553
+ // ---- Reporting and bytes -------------------------------------------------------
1554
+
1555
+ /** Internal: records an error over `[start, end)`. */
1556
+ error(code: i32, message: string, start: i32, end: i32): void {
1557
+ this.stream.report(code, false, message, start, end, this.doc)
1558
+ this.lastErrStart = start
1559
+ this.lastErrEnd = end < start ? start : end
1560
+ }
1561
+
1562
+ /** Internal: records a warning over `[start, end)`. */
1563
+ warn(code: i32, message: string, start: i32, end: i32): void {
1564
+ this.stream.report(code, true, message, start, end, this.doc)
1565
+ }
1566
+
1567
+ /** Internal: the byte at `i`, or `-1` outside the text. */
1568
+ at(i: i32): i32 {
1569
+ return yamlByte(this.text, i)
1570
+ }
1571
+
1572
+ /** Internal: whether `i` holds a `-` that starts a block sequence entry. */
1573
+ isSeqEntryDash(i: i32): boolean {
1574
+ return this.at(i) === YAML_DASH && yamlIsWs(this.at(i + 1))
1575
+ }
1576
+
1577
+ /** Internal: whether `[from, to)` holds a line break. */
1578
+ crossesLine(from: i32, to: i32): boolean {
1579
+ return from < to && yamlLineEnd(this.text, from, to) < to
1580
+ }
1581
+
1582
+ /** Internal: the start of the line after the one `from` is on, or the end of the text. */
1583
+ nextLineStart(from: i32): i32 {
1584
+ const e: i32 = yamlLineEnd(this.text, from, this.len)
1585
+ return e >= this.len ? this.len : yamlAfterBreak(this.text, e)
1586
+ }
1587
+
1588
+ /** Internal: whether `i` starts a `---` or `...` marker. */
1589
+ isDocMarker(i: i32): boolean {
1590
+ const c: i32 = this.at(i)
1591
+ return (
1592
+ (c === YAML_DASH || c === YAML_DOT) &&
1593
+ this.at(i + 1) === c &&
1594
+ this.at(i + 2) === c &&
1595
+ yamlIsWs(this.at(i + 3))
1596
+ )
1597
+ }
1598
+
1599
+ /** Internal: the end of `[from, …)` before any trailing blanks and a comment, on its line. */
1600
+ plainLineEnd(from: i32): i32 {
1601
+ let i: i32 = from
1602
+ let lastNonSpace: i32 = from
1603
+ while (i < this.len) {
1604
+ const c: i32 = this.at(i)
1605
+ if (yamlIsBreak(c)) {
1606
+ break
1607
+ }
1608
+ if (c === YAML_HASH && i > from && yamlIsBlank(this.at(i - 1))) {
1609
+ break
1610
+ }
1611
+ i += 1
1612
+ if (!yamlIsBlank(c)) {
1613
+ lastNonSpace = i
1614
+ }
1615
+ }
1616
+ return lastNonSpace
1617
+ }
1618
+
1619
+ /** Internal: the end of a token: the next blank, break or end of text. */
1620
+ tokenEnd(from: i32): i32 {
1621
+ let i: i32 = from
1622
+ while (i < this.len && !yamlIsWs(this.at(i))) {
1623
+ i += 1
1624
+ }
1625
+ return i
1626
+ }
1627
+
1628
+ /**
1629
+ * Internal: the end of an anchor or tag token; in flow, a flow indicator ends
1630
+ * it too, except in a verbatim tag, `!<…>`, which runs to its `>`.
1631
+ */
1632
+ propTokenEnd(from: i32, flow: boolean): i32 {
1633
+ let i: i32 = from
1634
+ if (this.at(from - 1) === YAML_BANG && this.at(from) === YAML_LT) {
1635
+ while (i < this.len && !yamlIsWs(this.at(i))) {
1636
+ i += 1
1637
+ if (this.at(i - 1) === YAML_GT) {
1638
+ return i
1639
+ }
1640
+ }
1641
+ return i
1642
+ }
1643
+ while (i < this.len) {
1644
+ const c: i32 = this.at(i)
1645
+ if (yamlIsWs(c) || (flow && yamlIsFlowIndicator(c))) {
1646
+ break
1647
+ }
1648
+ i += 1
1649
+ }
1650
+ return i
1651
+ }
1652
+
1653
+ // ---- Lines and comments ----------------------------------------------------------
1654
+
1655
+ /**
1656
+ * Internal: moves to the start of the next line with content, past blank and
1657
+ * comment lines, and sets `lineEof`, `lineIndent` and `lineContent`.
1658
+ */
1659
+ peekLine(minIndent: i32): void {
1660
+ let p: i32 = this.pos
1661
+ while (p < this.len) {
1662
+ let i: i32 = p
1663
+ while (this.at(i) === YAML_SPACE) {
1664
+ i += 1
1665
+ }
1666
+ const c: i32 = this.at(i)
1667
+ if (c < 0) {
1668
+ break
1669
+ }
1670
+ if (yamlIsBreak(c) || c === YAML_HASH) {
1671
+ if (c === YAML_HASH) {
1672
+ this.recordComment(i)
1673
+ }
1674
+ p = this.nextLineStart(i)
1675
+ continue
1676
+ }
1677
+ if (c === YAML_TAB) {
1678
+ let j: i32 = i
1679
+ while (yamlIsBlank(this.at(j))) {
1680
+ j += 1
1681
+ }
1682
+ const d: i32 = this.at(j)
1683
+ if (d < 0 || yamlIsBreak(d) || d === YAML_HASH) {
1684
+ if (d === YAML_HASH) {
1685
+ this.recordComment(j)
1686
+ }
1687
+ p = this.nextLineStart(j)
1688
+ continue
1689
+ }
1690
+ if (i - p < minIndent && this.tabReportedAt !== p) {
1691
+ this.error(YAML_ERR_TAB_INDENT, "Tabs cannot be used for indentation", i, j)
1692
+ this.tabReportedAt = p
1693
+ }
1694
+ this.setLine(p, j - p, j)
1695
+ return
1696
+ }
1697
+ this.setLine(p, i - p, i)
1698
+ return
1699
+ }
1700
+ this.pos = this.len
1701
+ this.lineEof = true
1702
+ this.lineIndent = 0
1703
+ this.lineContent = this.len
1704
+ }
1705
+
1706
+ /** Internal: the line `peekLine` found. */
1707
+ setLine(start: i32, indent: i32, content: i32): void {
1708
+ this.pos = start
1709
+ this.lineEof = false
1710
+ this.lineIndent = indent
1711
+ this.lineContent = content
1712
+ }
1713
+
1714
+ /** Internal: steps past spaces and tabs. */
1715
+ skipInlineSpaces(): void {
1716
+ while (yamlIsBlank(this.at(this.pos))) {
1717
+ this.pos += 1
1718
+ }
1719
+ }
1720
+
1721
+ /** Internal: steps past the spaces after an indicator, reporting a tab that indents a compact node. */
1722
+ skipIndicatorSeparation(): void {
1723
+ while (this.at(this.pos) === YAML_SPACE) {
1724
+ this.pos += 1
1725
+ }
1726
+ if (this.at(this.pos) !== YAML_TAB) {
1727
+ return
1728
+ }
1729
+ const tab: i32 = this.pos
1730
+ this.skipInlineSpaces()
1731
+ const c: i32 = this.at(this.pos)
1732
+ if (c === YAML_HASH) {
1733
+ return
1734
+ }
1735
+ if (
1736
+ this.isSeqEntryDash(this.pos) ||
1737
+ (c === YAML_QUESTION && yamlIsWs(this.at(this.pos + 1))) ||
1738
+ this.findKeyColon(this.pos) >= 0
1739
+ ) {
1740
+ this.error(YAML_ERR_TAB_INDENT, "Tabs cannot be used for indentation", tab, this.pos)
1741
+ }
1742
+ }
1743
+
1744
+ /** Internal: keeps the comment that starts at `hash`, when comments are kept. */
1745
+ recordComment(hash: i32): void {
1746
+ if (!this.keepComments || hash < this.commentWatermark) {
1747
+ return
1748
+ }
1749
+ const end: i32 = yamlLineEnd(this.text, hash, this.len)
1750
+ this.commentWatermark = end
1751
+ this.stream.commentStarts.push(hash)
1752
+ this.stream.commentEnds.push(end)
1753
+ }
1754
+
1755
+ /** Internal: keeps a comment after the content on this line, if there is one. */
1756
+ recordTrailingComment(): void {
1757
+ if (!this.keepComments) {
1758
+ return
1759
+ }
1760
+ let i: i32 = this.pos
1761
+ while (yamlIsBlank(this.at(i))) {
1762
+ i += 1
1763
+ }
1764
+ if (this.at(i) !== YAML_HASH) {
1765
+ return
1766
+ }
1767
+ if (i === this.pos && !yamlIsWs(this.at(i - 1))) {
1768
+ return
1769
+ }
1770
+ this.recordComment(i)
1771
+ }
1772
+
1773
+ /** Internal: keeps the first comment on the line from `from`, if there is one. */
1774
+ recordLineComment(from: i32): void {
1775
+ if (!this.keepComments) {
1776
+ return
1777
+ }
1778
+ let i: i32 = from
1779
+ while (i < this.len) {
1780
+ const c: i32 = this.at(i)
1781
+ if (yamlIsBreak(c)) {
1782
+ return
1783
+ }
1784
+ if (c === YAML_HASH && yamlIsWs(this.at(i - 1))) {
1785
+ this.recordComment(i)
1786
+ return
1787
+ }
1788
+ i += 1
1789
+ }
1790
+ }
1791
+
1792
+ /** Internal: whether the cursor is at the end of the text, a break or a comment. */
1793
+ atLineEnd(): boolean {
1794
+ const c: i32 = this.at(this.pos)
1795
+ return c < 0 || yamlIsBreak(c) || c === YAML_HASH
1796
+ }
1797
+
1798
+ /** Internal: moves past the rest of this line, keeping its comment. */
1799
+ finishLine(): void {
1800
+ this.recordTrailingComment()
1801
+ this.pos = this.nextLineStart(this.pos)
1802
+ }
1803
+
1804
+ /** Internal: `finishLine`, unless the cursor is already at the start of a line. */
1805
+ finishLineIfMidLine(): void {
1806
+ if (this.pos > 0 && this.pos < this.len && !yamlIsBreak(this.at(this.pos - 1))) {
1807
+ this.finishLine()
1808
+ }
1809
+ }
1810
+
1811
+ /** Internal: reports what follows a node on its line, if it is neither a comment nor nothing. */
1812
+ checkTrailingContent(): void {
1813
+ const c: i32 = this.at(this.pos)
1814
+ if (c < 0 || yamlIsBreak(c)) {
1815
+ return
1816
+ }
1817
+ if (yamlIsBlank(c)) {
1818
+ this.skipInlineSpaces()
1819
+ if (this.atLineEnd()) {
1820
+ return
1821
+ }
1822
+ }
1823
+ const start: i32 = this.pos
1824
+ if (this.at(start) === YAML_HASH) {
1825
+ if (start > 0 && yamlIsBlank(this.at(start - 1))) {
1826
+ return
1827
+ }
1828
+ this.error(
1829
+ YAML_ERR_BAD_COMMENT,
1830
+ "A comment must be preceded by whitespace",
1831
+ start,
1832
+ this.plainLineEnd(start)
1833
+ )
1834
+ return
1835
+ }
1836
+ this.error(
1837
+ YAML_ERR_UNEXPECTED_CONTENT,
1838
+ "Unexpected content after the end of a node",
1839
+ start,
1840
+ this.plainLineEnd(start)
1841
+ )
1842
+ }
1843
+
1844
+ // ---- Tags and properties ---------------------------------------------------------
1845
+
1846
+ /** Internal: the tag written at `[start, end)`, resolved against this document's handles. */
1847
+ resolveTag(start: i32, end: i32): string {
1848
+ const written: string = this.text.substring(start, end)
1849
+ if (end - start === 1) {
1850
+ return "!"
1851
+ }
1852
+ if (this.at(start + 1) === YAML_LT) {
1853
+ if (this.at(end - 1) !== YAML_GT) {
1854
+ this.error(YAML_ERR_BAD_TAG, 'Verbatim tag is missing its closing ">"', start, end)
1855
+ return written
1856
+ }
1857
+ if (end - start === 3) {
1858
+ this.error(YAML_ERR_BAD_TAG, "Verbatim tag is empty", start, end)
1859
+ return written
1860
+ }
1861
+ return yamlShortenTag(this.text.substring(start + 2, end - 1))
1862
+ }
1863
+ let handleEnd: i32 = start + 1
1864
+ if (this.at(start + 1) === YAML_BANG) {
1865
+ handleEnd = start + 2
1866
+ } else {
1867
+ let i: i32 = start + 1
1868
+ while (i < end && yamlIsHandleChar(this.at(i))) {
1869
+ i += 1
1870
+ }
1871
+ if (i > start + 1 && this.at(i) === YAML_BANG) {
1872
+ handleEnd = i + 1
1873
+ }
1874
+ }
1875
+ let k: i32 = handleEnd
1876
+ while (k < end) {
1877
+ if (yamlIsFlowIndicator(this.at(k))) {
1878
+ this.error(YAML_ERR_BAD_TAG, `Tag "${written}" contains a character a tag may not hold`, start, end)
1879
+ break
1880
+ }
1881
+ k += 1
1882
+ }
1883
+ const handle: string = this.text.substring(start, handleEnd)
1884
+ const suffix: string = this.text.substring(handleEnd, end)
1885
+ let i: i32 = toI32(this.handleNames.length) - 1
1886
+ while (i >= 0) {
1887
+ if (this.handleNames[i] === handle) {
1888
+ return yamlShortenTag(this.handlePrefixes[i] + suffix)
1889
+ }
1890
+ i -= 1
1891
+ }
1892
+ if (handle === "!!") {
1893
+ return suffix
1894
+ }
1895
+ if (handle === "!") {
1896
+ return written
1897
+ }
1898
+ this.error(
1899
+ YAML_ERR_UNKNOWN_TAG_HANDLE,
1900
+ `Tag handle "${handle}" was never declared by a %TAG directive`,
1901
+ start,
1902
+ end
1903
+ )
1904
+ return written
1905
+ }
1906
+
1907
+ /** Internal: reports an anchor or alias name that is empty or runs into a flow indicator. */
1908
+ reportBadAnchorName(what: string, start: i32, end: i32): void {
1909
+ if (end === start + 1) {
1910
+ this.error(YAML_ERR_BAD_ANCHOR, `${what} name cannot be empty`, start, end)
1911
+ return
1912
+ }
1913
+ const name: string = this.text.substring(start + 1, end)
1914
+ const next: string = this.text.substring(end, end + 1)
1915
+ this.error(
1916
+ YAML_ERR_BAD_ANCHOR,
1917
+ `${what} name "${name}" cannot run on into "${next}": a flow indicator is not part of a name, so it needs a space before it`,
1918
+ start,
1919
+ end + 1
1920
+ )
1921
+ }
1922
+
1923
+ /** Internal: warns about an anchor or alias name `[start + 1, end)` that ends in `:`. */
1924
+ checkAmbiguousName(what: string, start: i32, end: i32): void {
1925
+ if (end <= start + 1 || this.at(end - 1) !== YAML_COLON) {
1926
+ return
1927
+ }
1928
+ const name: string = this.text.substring(start + 1, end)
1929
+ this.warn(
1930
+ YAML_ERR_AMBIGUOUS_ANCHOR_NAME,
1931
+ `${what} name "${name}" ends in ":", which YAML reads as part of the name rather than as a mapping separator`,
1932
+ start,
1933
+ end
1934
+ )
1935
+ }
1936
+
1937
+ /** Internal: reads the anchor and tag before a node; answers their slot, or `-1` for none. */
1938
+ scanProps(flow: boolean): i32 {
1939
+ const first: i32 = this.at(this.pos)
1940
+ if (first !== YAML_AMP && first !== YAML_BANG && !yamlIsBlank(first)) {
1941
+ return -1
1942
+ }
1943
+ let slot: i32 = -1
1944
+ while (true) {
1945
+ this.skipInlineSpaces()
1946
+ const c: i32 = this.at(this.pos)
1947
+ if (c !== YAML_AMP && c !== YAML_BANG) {
1948
+ break
1949
+ }
1950
+ const start: i32 = this.pos
1951
+ const end: i32 = this.propTokenEnd(start + 1, c === YAML_AMP || flow)
1952
+ if (slot < 0) {
1953
+ slot = this.stream.newProps()
1954
+ yamlPut(this.stream.propStarts, slot, start)
1955
+ }
1956
+ if (c === YAML_AMP) {
1957
+ if (end === start + 1 || (!flow && yamlIsFlowIndicator(this.at(end)))) {
1958
+ this.reportBadAnchorName("Anchor", start, end)
1959
+ if (end === start + 1) {
1960
+ this.pos = end
1961
+ continue
1962
+ }
1963
+ }
1964
+ if (yamlAt(this.stream.propAnchorStarts, slot) >= 0) {
1965
+ this.error(YAML_ERR_BAD_PROPERTY, "A node cannot carry more than one anchor", start, end)
1966
+ }
1967
+ yamlPut(this.stream.propAnchorStarts, slot, start + 1)
1968
+ yamlPut(this.stream.propAnchorEnds, slot, end)
1969
+ this.checkAmbiguousName("Anchor", start, end)
1970
+ } else {
1971
+ if (yamlAt(this.stream.propTagStarts, slot) >= 0) {
1972
+ this.error(YAML_ERR_BAD_PROPERTY, "A node cannot carry more than one tag", start, end)
1973
+ }
1974
+ const tag: string = this.resolveTag(start, end)
1975
+ const tags: string[] = this.stream.propTags
1976
+ if (slot >= 0 && slot < toI32(tags.length)) {
1977
+ tags[slot] = tag
1978
+ }
1979
+ yamlPut(this.stream.propTagStarts, slot, start)
1980
+ yamlPut(this.stream.propTagEnds, slot, end)
1981
+ }
1982
+ yamlPut(this.stream.propEnds, slot, end)
1983
+ this.pos = end
1984
+ }
1985
+ if (slot >= 0 && yamlAt(this.stream.propAnchorStarts, slot) >= 0) {
1986
+ yamlStackPush(this.pendingEnds, this.pendingTop, yamlAt(this.stream.propAnchorEnds, slot))
1987
+ this.pendingTop = yamlStackPush(
1988
+ this.pendingStarts,
1989
+ this.pendingTop,
1990
+ yamlAt(this.stream.propAnchorStarts, slot)
1991
+ )
1992
+ }
1993
+ return slot
1994
+ }
1995
+
1996
+ /** Internal: the first byte after the anchors and tags at `from`, and the blanks after them. */
1997
+ skipPropsAhead(from: i32): i32 {
1998
+ let i: i32 = from
1999
+ while (true) {
2000
+ const c: i32 = this.at(i)
2001
+ if (c !== YAML_AMP && c !== YAML_BANG) {
2002
+ return i
2003
+ }
2004
+ i = this.propTokenEnd(i + 1, c === YAML_AMP)
2005
+ while (yamlIsBlank(this.at(i))) {
2006
+ i += 1
2007
+ }
2008
+ }
2009
+ }
2010
+
2011
+ /** Internal: the entry of the anchor named `[from, to)` among those defined so far, or `-1`. */
2012
+ findAnchor(from: i32, to: i32): i32 {
2013
+ let i: i32 = this.anchorTop - 1
2014
+ while (i >= 0) {
2015
+ if (yamlSameBytes(this.text, yamlAt(this.anchorStarts, i), yamlAt(this.anchorEnds, i), from, to)) {
2016
+ return i
2017
+ }
2018
+ i -= 1
2019
+ }
2020
+ return -1
2021
+ }
2022
+
2023
+ /** Internal: defines the anchor `[from, to)` as naming `node`, and ends it being pending. */
2024
+ defineAnchor(from: i32, to: i32, node: i32, slot: i32): void {
2025
+ yamlStackPush(this.anchorStarts, this.anchorTop, from)
2026
+ yamlStackPush(this.anchorEnds, this.anchorTop, to)
2027
+ yamlPut(this.stream.propEntries, slot, this.anchorTop)
2028
+ this.anchorTop = yamlStackPush(this.anchorNodes, this.anchorTop, node)
2029
+ let i: i32 = this.pendingTop - 1
2030
+ while (i >= 0) {
2031
+ if (yamlSameBytes(this.text, yamlAt(this.pendingStarts, i), yamlAt(this.pendingEnds, i), from, to)) {
2032
+ // Close the gap: what was pending after it moves down one.
2033
+ let j: i32 = i + 1
2034
+ while (j < this.pendingTop) {
2035
+ yamlPut(this.pendingStarts, j - 1, yamlAt(this.pendingStarts, j))
2036
+ yamlPut(this.pendingEnds, j - 1, yamlAt(this.pendingEnds, j))
2037
+ j += 1
2038
+ }
2039
+ this.pendingTop -= 1
2040
+ return
2041
+ }
2042
+ i -= 1
2043
+ }
2044
+ }
2045
+
2046
+ /** Internal: gives `node` the properties in `slot`, and answers `node`. */
2047
+ attachProps(node: i32, slot: i32): i32 {
2048
+ if (slot < 0 || node < 0) {
2049
+ return node
2050
+ }
2051
+ const stream: YamlStream = this.stream
2052
+ const anchorFrom: i32 = yamlAt(stream.propAnchorStarts, slot)
2053
+ const anchorTo: i32 = yamlAt(stream.propAnchorEnds, slot)
2054
+ if (stream.kind(node) === YAML_ALIAS) {
2055
+ this.error(
2056
+ YAML_ERR_BAD_PROPERTY,
2057
+ "An alias cannot carry an anchor or a tag",
2058
+ stream.start(node),
2059
+ stream.end(node)
2060
+ )
2061
+ if (anchorFrom >= 0) {
2062
+ yamlPut(stream.props, node, slot)
2063
+ this.defineAnchor(anchorFrom, anchorTo, node, slot)
2064
+ }
2065
+ return node
2066
+ }
2067
+ const own: i32 = yamlAt(stream.props, node)
2068
+ let into: i32 = slot
2069
+ if (own >= 0) {
2070
+ // The node read its own properties on a later line: these join them.
2071
+ into = own
2072
+ if (anchorFrom >= 0) {
2073
+ if (yamlAt(stream.propAnchorStarts, own) >= 0) {
2074
+ this.error(
2075
+ YAML_ERR_BAD_PROPERTY,
2076
+ "A node cannot carry more than one anchor",
2077
+ stream.start(node),
2078
+ stream.end(node)
2079
+ )
2080
+ }
2081
+ yamlPut(stream.propAnchorStarts, own, anchorFrom)
2082
+ yamlPut(stream.propAnchorEnds, own, anchorTo)
2083
+ }
2084
+ if (yamlAt(stream.propTagStarts, slot) >= 0) {
2085
+ yamlPut(stream.propTagStarts, own, yamlAt(stream.propTagStarts, slot))
2086
+ yamlPut(stream.propTagEnds, own, yamlAt(stream.propTagEnds, slot))
2087
+ const tags: string[] = stream.propTags
2088
+ if (own < toI32(tags.length) && slot < toI32(tags.length)) {
2089
+ tags[own] = tags[slot]
2090
+ }
2091
+ }
2092
+ yamlPut(stream.propStarts, own, yamlAt(stream.propStarts, slot))
2093
+ } else {
2094
+ yamlPut(stream.props, node, slot)
2095
+ }
2096
+ if (anchorFrom >= 0) {
2097
+ this.defineAnchor(anchorFrom, anchorTo, node, into)
2098
+ }
2099
+ if (yamlAt(stream.propTagStarts, slot) >= 0) {
2100
+ yamlPutU8(stream.types, node, YAML_UNRESOLVED)
2101
+ this.checkTaggedNode(node, slot)
2102
+ }
2103
+ return node
2104
+ }
2105
+
2106
+ // ---- Scalars -------------------------------------------------------------------
2107
+
2108
+ /** Internal: reports the bad escape whose `\` is at `at`. */
2109
+ reportBadEscape(at: i32): void {
2110
+ const e: i32 = this.at(at + 1)
2111
+ let digits: i32 = 0
2112
+ if (e === 120) {
2113
+ digits = 2
2114
+ } else if (e === 117) {
2115
+ digits = 4
2116
+ } else if (e === 85) {
2117
+ digits = 8
2118
+ }
2119
+ if (digits > 0) {
2120
+ let limit: i32 = at + 2 + digits
2121
+ if (limit > this.len) {
2122
+ limit = this.len
2123
+ }
2124
+ let end: i32 = at + 2
2125
+ let value: i32 = 0
2126
+ let tooBig: boolean = false
2127
+ while (end < limit && yamlHexValue(this.at(end)) >= 0) {
2128
+ // 0x10FFFF fits in 21 bits: past it, stop growing and remember.
2129
+ if (value > 1114111) {
2130
+ tooBig = true
2131
+ } else {
2132
+ value = value * 16 + yamlHexValue(this.at(end))
2133
+ }
2134
+ end += 1
2135
+ }
2136
+ const written: string = this.text.substring(at, end)
2137
+ if (end - at - 2 < digits) {
2138
+ this.error(YAML_ERR_BAD_ESCAPE, `"${written}" needs exactly ${digits} hex digits`, at, end)
2139
+ } else if (digits === 8 && (tooBig || value > 1114111)) {
2140
+ this.error(YAML_ERR_BAD_ESCAPE, `"${written}" is past the last Unicode code point`, at, end)
2141
+ }
2142
+ return
2143
+ }
2144
+ const width: i32 = yamlCharWidth(e)
2145
+ const escaped: string = this.text.substring(at + 1, at + 1 + width)
2146
+ this.error(YAML_ERR_BAD_ESCAPE, `"\\${escaped}" is not a valid escape sequence`, at, at + 1 + width)
2147
+ }
2148
+
2149
+ /** Internal: reports a quoted scalar's continuation line at `lineStart` that is not indented past its parent. */
2150
+ checkQuotedIndent(lineStart: i32, parentIndent: i32): boolean {
2151
+ let i: i32 = lineStart
2152
+ while (this.at(i) === YAML_SPACE) {
2153
+ i += 1
2154
+ }
2155
+ const c: i32 = this.at(i)
2156
+ if (c < 0 || yamlIsBreak(c) || i - lineStart > parentIndent) {
2157
+ return false
2158
+ }
2159
+ this.flowIndentReported = true
2160
+ this.error(
2161
+ YAML_ERR_BAD_INDENT,
2162
+ "A quoted scalar continued over lines must be indented deeper than its parent",
2163
+ lineStart,
2164
+ i
2165
+ )
2166
+ return true
2167
+ }
2168
+
2169
+ /** Internal: reads a single- or double-quoted scalar at the cursor. */
2170
+ scanQuoted(quote: i32, parentIndent: i32): i32 {
2171
+ const start: i32 = this.pos
2172
+ const single: boolean = quote === YAML_SQUOTE
2173
+ let i: i32 = start + 1
2174
+ let indentReported: boolean = parentIndent < 0
2175
+ let closed: boolean = false
2176
+ let contentEnd: i32 = this.len
2177
+ while (i < this.len) {
2178
+ const at: i32 = toI32(indexOfAny(this.text, single ? YAML_SINGLE_STOPS : YAML_DOUBLE_STOPS, i))
2179
+ if (at < 0) {
2180
+ i = this.len
2181
+ break
2182
+ }
2183
+ i = at
2184
+ const c: i32 = this.at(i)
2185
+ if (c === YAML_SQUOTE) {
2186
+ if (this.at(i + 1) === YAML_SQUOTE) {
2187
+ i += 2
2188
+ continue
2189
+ }
2190
+ contentEnd = i
2191
+ i += 1
2192
+ closed = true
2193
+ break
2194
+ }
2195
+ if (c === YAML_DQUOTE) {
2196
+ contentEnd = i
2197
+ i += 1
2198
+ closed = true
2199
+ break
2200
+ }
2201
+ if (c === YAML_BACKSLASH) {
2202
+ const e: i32 = this.at(i + 1)
2203
+ if (e < 0) {
2204
+ i = this.len
2205
+ break
2206
+ }
2207
+ if (yamlIsBreak(e)) {
2208
+ i += 1
2209
+ continue
2210
+ }
2211
+ if (yamlEscapeKind(e) !== 1) {
2212
+ this.reportBadEscape(i)
2213
+ }
2214
+ i += 2
2215
+ continue
2216
+ }
2217
+ // A line break.
2218
+ const after: i32 = yamlAfterBreak(this.text, i)
2219
+ if (this.isDocMarker(after)) {
2220
+ contentEnd = i
2221
+ i = after
2222
+ break
2223
+ }
2224
+ if (!indentReported) {
2225
+ indentReported = this.checkQuotedIndent(after, parentIndent)
2226
+ }
2227
+ i = after
2228
+ }
2229
+ if (!closed) {
2230
+ this.error(YAML_ERR_UNTERMINATED_QUOTE, "Missing closing quote", start, i)
2231
+ }
2232
+ const node: i32 = this.stream.newNode(
2233
+ YAML_SCALAR,
2234
+ single ? YAML_SINGLE_QUOTED : YAML_DOUBLE_QUOTED,
2235
+ start,
2236
+ i
2237
+ )
2238
+ yamlPut(this.stream.firsts, node, start + 1)
2239
+ yamlPut(this.stream.counts, node, contentEnd)
2240
+ this.pos = i
2241
+ return node
2242
+ }
2243
+
2244
+ /** Internal: reads an alias at the cursor and resolves it against the anchors defined so far. */
2245
+ scanAlias(): i32 {
2246
+ const start: i32 = this.pos
2247
+ let i: i32 = start + 1
2248
+ while (i < this.len) {
2249
+ const c: i32 = this.at(i)
2250
+ if (yamlIsWs(c) || yamlIsFlowIndicator(c)) {
2251
+ break
2252
+ }
2253
+ i += 1
2254
+ }
2255
+ this.checkAmbiguousName("Alias", start, i)
2256
+ this.pos = i
2257
+ const node: i32 = this.stream.newNode(YAML_ALIAS, YAML_PLAIN, start, i)
2258
+ if (i === start + 1) {
2259
+ this.reportBadAnchorName("Alias", start, i)
2260
+ return node
2261
+ }
2262
+ const entry: i32 = this.findAnchor(start + 1, i)
2263
+ if (entry >= 0) {
2264
+ yamlPut(this.stream.firsts, node, yamlAt(this.anchorNodes, entry))
2265
+ return node
2266
+ }
2267
+ const name: string = this.text.substring(start + 1, i)
2268
+ let p: i32 = this.pendingTop - 1
2269
+ while (p >= 0) {
2270
+ if (
2271
+ yamlSameBytes(this.text, yamlAt(this.pendingStarts, p), yamlAt(this.pendingEnds, p), start + 1, i)
2272
+ ) {
2273
+ this.error(
2274
+ YAML_ERR_RECURSIVE_ALIAS,
2275
+ `Alias "*${name}" refers to the node that contains it; recursive structures are not supported`,
2276
+ start,
2277
+ i
2278
+ )
2279
+ return node
2280
+ }
2281
+ p -= 1
2282
+ }
2283
+ this.error(YAML_ERR_UNRESOLVED_ALIAS, `Alias "*${name}" has no matching anchor`, start, i)
2284
+ return node
2285
+ }
2286
+
2287
+ /** Internal: an empty plain scalar, a null, at `at`. */
2288
+ emptyScalar(at: i32): i32 {
2289
+ return this.stream.newNode(YAML_SCALAR, YAML_PLAIN, at, at)
2290
+ }
2291
+
2292
+ /** Internal: whether `code` may not start a plain scalar: `1` always, `2` for `-?:` before a blank. */
2293
+ plainStartClass(code: i32): i32 {
2294
+ if (code === YAML_DASH || code === YAML_QUESTION || code === YAML_COLON) {
2295
+ return 2
2296
+ }
2297
+ if (
2298
+ yamlIsFlowIndicator(code) ||
2299
+ code === YAML_HASH ||
2300
+ code === YAML_AMP ||
2301
+ code === YAML_STAR ||
2302
+ code === YAML_BANG ||
2303
+ code === YAML_PIPE ||
2304
+ code === YAML_GT ||
2305
+ code === YAML_SQUOTE ||
2306
+ code === YAML_DQUOTE ||
2307
+ code === YAML_PERCENT ||
2308
+ code === YAML_AT ||
2309
+ code === YAML_BACKTICK
2310
+ ) {
2311
+ return 1
2312
+ }
2313
+ return 0
2314
+ }
2315
+
2316
+ /** Internal: whether `after` ends a flow indicator: the end, a blank, a break, `,`, `]` or `}`. */
2317
+ flowIndicatorBoundary(after: i32): boolean {
2318
+ const c: i32 = this.at(after)
2319
+ return yamlIsWs(c) || c === YAML_COMMA || c === YAML_RBRACKET || c === YAML_RBRACE
2320
+ }
2321
+
2322
+ /** Internal: reports the indicator at `pos` that cannot start a plain scalar. */
2323
+ reportBadPlainStart(pos: i32, flow: boolean): void {
2324
+ if (this.lastErrStart <= pos && pos < this.lastErrEnd) {
2325
+ return
2326
+ }
2327
+ const c: i32 = this.at(pos)
2328
+ if (this.plainStartClass(c) === 2) {
2329
+ if (c === YAML_COLON) {
2330
+ return
2331
+ }
2332
+ if (flow ? !this.flowIndicatorBoundary(pos + 1) : !yamlIsWs(this.at(pos + 1))) {
2333
+ return
2334
+ }
2335
+ if (c === YAML_DASH) {
2336
+ if (flow) {
2337
+ this.error(
2338
+ YAML_ERR_BAD_SCALAR_START,
2339
+ 'A "-" sequence indicator cannot start a flow collection entry',
2340
+ pos,
2341
+ pos + 1
2342
+ )
2343
+ } else {
2344
+ this.error(
2345
+ YAML_ERR_UNEXPECTED_CONTENT,
2346
+ "A block sequence cannot start on the line of the key it belongs to",
2347
+ pos,
2348
+ pos + 1
2349
+ )
2350
+ }
2351
+ return
2352
+ }
2353
+ this.error(
2354
+ YAML_ERR_BAD_SCALAR_START,
2355
+ '"?" is an explicit-key indicator here and cannot start a plain scalar — quote the value',
2356
+ pos,
2357
+ pos + 1
2358
+ )
2359
+ return
2360
+ }
2361
+ if (flow && yamlIsFlowIndicator(c)) {
2362
+ return
2363
+ }
2364
+ const written: string = this.text.substring(pos, pos + 1)
2365
+ this.error(
2366
+ YAML_ERR_BAD_SCALAR_START,
2367
+ `Indicator "${written}" cannot start a plain scalar — quote the value`,
2368
+ pos,
2369
+ pos + 1
2370
+ )
2371
+ }
2372
+
2373
+ /** Internal: the first `:` in `[from, end)` that a blank or the end follows, or `-1`. */
2374
+ plainColonAt(from: i32, end: i32): i32 {
2375
+ let i: i32 = from
2376
+ while (i < end) {
2377
+ if (this.at(i) === YAML_COLON && (i + 1 >= end || yamlIsBlank(this.at(i + 1)))) {
2378
+ return i
2379
+ }
2380
+ i += 1
2381
+ }
2382
+ return -1
2383
+ }
2384
+
2385
+ /** Internal: reports the `: ` at `at` inside a plain scalar. */
2386
+ reportPlainColon(at: i32): void {
2387
+ this.error(
2388
+ YAML_ERR_BAD_SCALAR_CONTENT,
2389
+ 'A plain scalar cannot contain ": " — quote the value, or indent it as a nested mapping',
2390
+ at,
2391
+ at + 1
2392
+ )
2393
+ }
2394
+
2395
+ /** Internal: whether only blanks and a comment follow `from` on its line. */
2396
+ endsAtComment(from: i32): boolean {
2397
+ let i: i32 = from
2398
+ while (yamlIsBlank(this.at(i))) {
2399
+ i += 1
2400
+ }
2401
+ return this.at(i) === YAML_HASH
2402
+ }
2403
+
2404
+ /** Internal: reads a block-context plain scalar at the cursor, with its continuation lines. */
2405
+ scanPlainScalar(parentIndent: i32): i32 {
2406
+ const start: i32 = this.pos
2407
+ let valueEnd: i32 = this.plainLineEnd(start)
2408
+ let colonReported: boolean = false
2409
+ const firstColon: i32 = this.plainColonAt(start, valueEnd)
2410
+ if (firstColon >= 0) {
2411
+ this.reportPlainColon(firstColon)
2412
+ colonReported = true
2413
+ }
2414
+ let scan: i32 = this.nextLineStart(valueEnd)
2415
+ while (scan < this.len) {
2416
+ let i: i32 = scan
2417
+ while (this.at(i) === YAML_SPACE) {
2418
+ i += 1
2419
+ }
2420
+ const c: i32 = this.at(i)
2421
+ // A line of blanks is an empty line, tabs included, though a tab cannot indent.
2422
+ let blank: i32 = i
2423
+ while (yamlIsBlank(this.at(blank))) {
2424
+ blank += 1
2425
+ }
2426
+ if (yamlIsBreak(this.at(blank))) {
2427
+ scan = this.nextLineStart(blank)
2428
+ continue
2429
+ }
2430
+ if (this.at(blank) < 0) {
2431
+ break
2432
+ }
2433
+ const indent: i32 = i - scan
2434
+ if (indent <= parentIndent || c === YAML_HASH) {
2435
+ break
2436
+ }
2437
+ if (this.endsAtComment(valueEnd)) {
2438
+ break
2439
+ }
2440
+ if (indent === 0 && this.isDocMarker(i)) {
2441
+ break
2442
+ }
2443
+ const lineEnd: i32 = this.plainLineEnd(i)
2444
+ if (!colonReported) {
2445
+ const colon: i32 = this.plainColonAt(i, lineEnd)
2446
+ if (colon >= 0) {
2447
+ this.reportPlainColon(colon)
2448
+ colonReported = true
2449
+ }
2450
+ }
2451
+ valueEnd = lineEnd
2452
+ scan = this.nextLineStart(lineEnd)
2453
+ }
2454
+ this.pos = valueEnd
2455
+ const node: i32 = this.stream.newNode(YAML_SCALAR, YAML_PLAIN, start, valueEnd)
2456
+ yamlPut(this.stream.firsts, node, start)
2457
+ yamlPut(this.stream.counts, node, valueEnd)
2458
+ return node
2459
+ }
2460
+
2461
+ /** Internal: reports anything but a comment after a block scalar's header. */
2462
+ checkBlockHeaderEnd(): void {
2463
+ let i: i32 = this.pos
2464
+ while (yamlIsBlank(this.at(i))) {
2465
+ i += 1
2466
+ }
2467
+ const c: i32 = this.at(i)
2468
+ if (c < 0 || yamlIsBreak(c) || (c === YAML_HASH && i > this.pos)) {
2469
+ return
2470
+ }
2471
+ this.error(
2472
+ YAML_ERR_BAD_BLOCK_HEADER,
2473
+ c === YAML_HASH
2474
+ ? "A comment must be preceded by whitespace"
2475
+ : "A block scalar header holds only its chomping and indentation indicators",
2476
+ i,
2477
+ this.plainLineEnd(i)
2478
+ )
2479
+ }
2480
+
2481
+ /** Internal: reads a `|` or `>` block scalar at the cursor. */
2482
+ scanBlockScalar(parentIndent: i32): i32 {
2483
+ const start: i32 = this.pos
2484
+ const folded: boolean = this.at(start) === YAML_GT
2485
+ this.pos += 1
2486
+ let chomp: i32 = YAML_CLIP
2487
+ let explicitIndent: i32 = 0
2488
+ let sawChomp: boolean = false
2489
+ while (true) {
2490
+ const c: i32 = this.at(this.pos)
2491
+ if (c === YAML_DASH || c === YAML_PLUS) {
2492
+ if (sawChomp) {
2493
+ this.error(YAML_ERR_BAD_BLOCK_HEADER, "Repeated chomping indicator", this.pos, this.pos + 1)
2494
+ }
2495
+ sawChomp = true
2496
+ chomp = c === YAML_DASH ? YAML_STRIP : YAML_KEEP
2497
+ } else if (c >= YAML_ONE && c <= YAML_NINE) {
2498
+ if (explicitIndent !== 0) {
2499
+ this.error(YAML_ERR_BAD_BLOCK_HEADER, "Repeated indentation indicator", this.pos, this.pos + 1)
2500
+ }
2501
+ explicitIndent = c - YAML_ZERO
2502
+ } else {
2503
+ break
2504
+ }
2505
+ this.pos += 1
2506
+ }
2507
+ // An empty scalar ends at its header, not at the line after it.
2508
+ let valueEnd: i32 = this.pos
2509
+ this.checkBlockHeaderEnd()
2510
+ this.finishLine()
2511
+ const bodyStart: i32 = this.pos
2512
+ let contentIndent: i32 =
2513
+ explicitIndent > 0 ? (parentIndent > 0 ? parentIndent : toI32(0)) + explicitIndent : -1
2514
+ let leadingBlank: i32 = -1
2515
+ while (this.pos < this.len) {
2516
+ const lineStart: i32 = this.pos
2517
+ let i: i32 = lineStart
2518
+ while (this.at(i) === YAML_SPACE) {
2519
+ i += 1
2520
+ }
2521
+ const c: i32 = this.at(i)
2522
+ const indent: i32 = i - lineStart
2523
+ if (c === YAML_TAB && indent < (contentIndent === -1 ? parentIndent + 1 : contentIndent)) {
2524
+ this.error(YAML_ERR_TAB_INDENT, "Tabs cannot be used for indentation", i, i + 1)
2525
+ }
2526
+ if (c < 0 || yamlIsBreak(c)) {
2527
+ if (contentIndent === -1 && indent > leadingBlank) {
2528
+ leadingBlank = indent
2529
+ }
2530
+ this.pos = this.nextLineStart(i)
2531
+ continue
2532
+ }
2533
+ if (indent === 0 && this.isDocMarker(i)) {
2534
+ break
2535
+ }
2536
+ if (contentIndent === -1) {
2537
+ if (indent <= parentIndent) {
2538
+ break
2539
+ }
2540
+ contentIndent = indent
2541
+ if (leadingBlank > indent) {
2542
+ this.error(
2543
+ YAML_ERR_BAD_INDENT,
2544
+ "A leading blank line of a block scalar is indented deeper than its first content line",
2545
+ lineStart,
2546
+ i
2547
+ )
2548
+ }
2549
+ }
2550
+ if (indent < contentIndent) {
2551
+ break
2552
+ }
2553
+ const lineEnd: i32 = yamlLineEnd(this.text, lineStart + contentIndent, this.len)
2554
+ valueEnd = lineEnd
2555
+ this.pos = this.nextLineStart(lineEnd)
2556
+ }
2557
+ const node: i32 = this.stream.newNode(YAML_SCALAR, folded ? YAML_FOLDED : YAML_LITERAL, start, valueEnd)
2558
+ yamlPut(this.stream.firsts, node, bodyStart)
2559
+ yamlPut(this.stream.counts, node, this.pos)
2560
+ const eight: i32 = 8
2561
+ const four: i32 = 4
2562
+ yamlPut(this.stream.extras, node, (contentIndent + 1) * eight + (explicitIndent > 0 ? four : 0) + chomp)
2563
+ return node
2564
+ }
2565
+
2566
+ // ---- Directives and key colons -------------------------------------------------
2567
+
2568
+ /** Internal: reads the `%` directive at `at` into the document's handles and version. */
2569
+ readDirective(at: i32): void {
2570
+ const nameEnd: i32 = this.tokenEnd(at + 1)
2571
+ const isTag: boolean = yamlIsWord(this.text, at + 1, nameEnd, "TAG")
2572
+ if (isTag || yamlIsWord(this.text, at + 1, nameEnd, "YAML")) {
2573
+ let i: i32 = nameEnd
2574
+ while (yamlIsBlank(this.at(i))) {
2575
+ i += 1
2576
+ }
2577
+ const firstEnd: i32 = this.tokenEnd(i)
2578
+ if (isTag) {
2579
+ let j: i32 = firstEnd
2580
+ while (yamlIsBlank(this.at(j))) {
2581
+ j += 1
2582
+ }
2583
+ const prefixEnd: i32 = this.tokenEnd(j)
2584
+ if (firstEnd === i || prefixEnd === j || this.at(i) !== YAML_BANG) {
2585
+ this.warn(YAML_ERR_BAD_DIRECTIVE, "%TAG directive needs a handle and a prefix", at, prefixEnd)
2586
+ return
2587
+ }
2588
+ const handle: string = this.text.substring(i, firstEnd)
2589
+ let k: i32 = toI32(this.handleNames.length) - 1
2590
+ while (k >= 0) {
2591
+ if (this.handleNames[k] === handle) {
2592
+ this.warn(
2593
+ YAML_ERR_DUPLICATE_DIRECTIVE,
2594
+ `Tag handle "${handle}" is declared more than once`,
2595
+ at,
2596
+ prefixEnd
2597
+ )
2598
+ break
2599
+ }
2600
+ k -= 1
2601
+ }
2602
+ this.handleNames.push(handle)
2603
+ this.handlePrefixes.push(this.text.substring(j, prefixEnd))
2604
+ return
2605
+ }
2606
+ this.readYamlVersion(at, i, firstEnd)
2607
+ return
2608
+ }
2609
+ this.warn(
2610
+ YAML_ERR_UNKNOWN_DIRECTIVE,
2611
+ `Unknown directive "%${this.text.substring(at + 1, nameEnd)}"`,
2612
+ at,
2613
+ nameEnd
2614
+ )
2615
+ }
2616
+
2617
+ /** Internal: checks the version `[from, to)` of the `%YAML` directive at `at`. */
2618
+ readYamlVersion(at: i32, from: i32, to: i32): void {
2619
+ if (this.yamlDirective) {
2620
+ this.error(YAML_ERR_DUPLICATE_DIRECTIVE, "A document may carry only one %YAML directive", at, to)
2621
+ }
2622
+ this.yamlDirective = true
2623
+ const version: string = this.text.substring(from, to)
2624
+ let i: i32 = from
2625
+ while (i < to && yamlIsDigit(this.at(i))) {
2626
+ i += 1
2627
+ }
2628
+ const major: i32 = i
2629
+ let valid: boolean = i > from && this.at(i) === YAML_DOT
2630
+ if (valid) {
2631
+ i += 1
2632
+ const minor: i32 = i
2633
+ while (i < to && yamlIsDigit(this.at(i))) {
2634
+ i += 1
2635
+ }
2636
+ valid = i > minor && i === to
2637
+ }
2638
+ if (!valid) {
2639
+ this.error(YAML_ERR_BAD_DIRECTIVE, `"${version}" is not a valid YAML version`, at, to)
2640
+ return
2641
+ }
2642
+ if (!this.lineRestIsBlank(to)) {
2643
+ let after: i32 = to
2644
+ while (yamlIsBlank(this.at(after))) {
2645
+ after += 1
2646
+ }
2647
+ this.error(
2648
+ YAML_ERR_BAD_DIRECTIVE,
2649
+ "Unexpected content after the %YAML version",
2650
+ after,
2651
+ this.tokenEnd(after)
2652
+ )
2653
+ }
2654
+ if (version !== "1.2") {
2655
+ const message: string =
2656
+ major === from + 1 && this.at(from) === YAML_ONE
2657
+ ? `Document declares YAML ${version}; it is parsed with the 1.2 core schema`
2658
+ : `Unsupported YAML version "${version}"`
2659
+ this.warn(YAML_ERR_UNSUPPORTED_YAML_VERSION, message, at, to)
2660
+ }
2661
+ }
2662
+
2663
+ /** Internal: the end of the quoted scalar opening at `from`, past its closing quote, or `-1` at a break. */
2664
+ quotedKeyEnd(from: i32, stopAtBreak: boolean): i32 {
2665
+ const quote: i32 = this.at(from)
2666
+ let i: i32 = from + 1
2667
+ while (i < this.len) {
2668
+ const q: i32 = this.at(i)
2669
+ if (stopAtBreak && yamlIsBreak(q)) {
2670
+ return -1
2671
+ }
2672
+ if (q === quote) {
2673
+ if (quote === YAML_SQUOTE && this.at(i + 1) === YAML_SQUOTE) {
2674
+ i += 2
2675
+ } else {
2676
+ return i + 1
2677
+ }
2678
+ } else {
2679
+ i += quote === YAML_DQUOTE && q === YAML_BACKSLASH ? 2 : 1
2680
+ }
2681
+ }
2682
+ return i + 1
2683
+ }
2684
+
2685
+ /** Internal: the end of the flow collection opening at `from`, when it closes on its line; else `-1`. */
2686
+ flowKeyEnd(from: i32): i32 {
2687
+ let depth: i32 = 0
2688
+ let i: i32 = from
2689
+ while (i < this.len) {
2690
+ const c: i32 = this.at(i)
2691
+ if (yamlIsBreak(c)) {
2692
+ return -1
2693
+ }
2694
+ if (c === YAML_SQUOTE || c === YAML_DQUOTE) {
2695
+ i = this.quotedKeyEnd(i, true)
2696
+ if (i < 0) {
2697
+ return -1
2698
+ }
2699
+ continue
2700
+ }
2701
+ if (c === YAML_LBRACKET || c === YAML_LBRACE) {
2702
+ depth += 1
2703
+ } else if (c === YAML_RBRACKET || c === YAML_RBRACE) {
2704
+ depth -= 1
2705
+ if (depth === 0) {
2706
+ return i + 1
2707
+ }
2708
+ }
2709
+ i += 1
2710
+ }
2711
+ return -1
2712
+ }
2713
+
2714
+ /** Internal: the `:` that makes the line from `from` an implicit key, or `-1`. */
2715
+ findKeyColon(from: i32): i32 {
2716
+ let i: i32 = from
2717
+ const first: i32 = this.at(from)
2718
+ if (first === YAML_LBRACKET || first === YAML_LBRACE) {
2719
+ i = this.flowKeyEnd(from)
2720
+ if (i < 0) {
2721
+ return -1
2722
+ }
2723
+ } else if (first === YAML_SQUOTE || first === YAML_DQUOTE) {
2724
+ i = this.quotedKeyEnd(from, false)
2725
+ }
2726
+ while (i < this.len) {
2727
+ const c: i32 = this.at(i)
2728
+ if (yamlIsBreak(c)) {
2729
+ return -1
2730
+ }
2731
+ if (c === YAML_HASH && i > from && yamlIsBlank(this.at(i - 1))) {
2732
+ return -1
2733
+ }
2734
+ if (c === YAML_COLON && yamlIsWs(this.at(i + 1))) {
2735
+ return i
2736
+ }
2737
+ i += 1
2738
+ }
2739
+ return -1
2740
+ }
2741
+
2742
+ /** Internal: whether the rest of the line from `from` is blank or a comment. */
2743
+ lineRestIsBlank(from: i32): boolean {
2744
+ let i: i32 = from
2745
+ while (yamlIsBlank(this.at(i))) {
2746
+ i += 1
2747
+ }
2748
+ const c: i32 = this.at(i)
2749
+ return c < 0 || yamlIsBreak(c) || c === YAML_HASH
2750
+ }
2751
+
2752
+ /** Internal: `to` moved back over the blanks that end `[from, to)`. */
2753
+ trimEnd(from: i32, to: i32): i32 {
2754
+ let e: i32 = to
2755
+ while (e > from && yamlIsBlank(this.at(e - 1))) {
2756
+ e -= 1
2757
+ }
2758
+ return e
2759
+ }
2760
+
2761
+ /** Internal: whether `at` holds a `?` explicit-key indicator. */
2762
+ isExplicitKey(at: i32): boolean {
2763
+ return this.at(at) === YAML_QUESTION && yamlIsWs(this.at(at + 1))
2764
+ }
2765
+
2766
+ // ---- Flow collections ----------------------------------------------------------
2767
+
2768
+ /** Internal: enters a flow collection nested in a block node indented `blockIndent`. */
2769
+ enterFlow(blockIndent: i32): void {
2770
+ this.flowIndent = blockIndent
2771
+ this.flowIndentReported = false
2772
+ }
2773
+
2774
+ /** Internal: checks that the flow line starting at `lineStart` is indented deeper than its block parent. */
2775
+ checkFlowIndent(lineStart: i32): void {
2776
+ let i: i32 = lineStart
2777
+ while (this.at(i) === YAML_SPACE) {
2778
+ i += 1
2779
+ }
2780
+ let c: i32 = this.at(i)
2781
+ if (c === YAML_TAB) {
2782
+ let j: i32 = i
2783
+ while (yamlIsBlank(this.at(j))) {
2784
+ j += 1
2785
+ }
2786
+ if (j >= this.len) {
2787
+ return
2788
+ }
2789
+ c = this.at(j)
2790
+ }
2791
+ if (c < 0 || yamlIsBreak(c) || c === YAML_HASH) {
2792
+ return
2793
+ }
2794
+ const indent: i32 = i - lineStart
2795
+ if (indent > this.flowIndent) {
2796
+ return
2797
+ }
2798
+ if ((c === YAML_RBRACKET || c === YAML_RBRACE) && indent >= this.flowIndent) {
2799
+ return
2800
+ }
2801
+ this.flowIndentReported = true
2802
+ this.error(
2803
+ YAML_ERR_BAD_INDENT,
2804
+ "A flow collection continued over lines must be indented deeper than its parent",
2805
+ lineStart,
2806
+ i
2807
+ )
2808
+ }
2809
+
2810
+ /** Internal: skips blanks, breaks and comments inside a flow collection. */
2811
+ skipFlowWs(): void {
2812
+ let p: i32 = this.pos
2813
+ while (p < this.len) {
2814
+ const c: i32 = this.at(p)
2815
+ if (yamlIsBlank(c)) {
2816
+ p += 1
2817
+ } else if (yamlIsBreak(c)) {
2818
+ p = yamlAfterBreak(this.text, p)
2819
+ const d: i32 = this.at(p)
2820
+ if ((d === YAML_DASH || d === YAML_DOT) && this.isDocMarker(p)) {
2821
+ this.error(
2822
+ YAML_ERR_UNEXPECTED_CONTENT,
2823
+ "A document marker cannot appear inside a flow collection",
2824
+ p,
2825
+ p + 3
2826
+ )
2827
+ } else if (!this.flowIndentReported) {
2828
+ this.checkFlowIndent(p)
2829
+ }
2830
+ } else if (c === YAML_HASH) {
2831
+ if (p > 0 && !yamlIsWs(this.at(p - 1))) {
2832
+ this.error(
2833
+ YAML_ERR_BAD_COMMENT,
2834
+ "A comment must be preceded by whitespace",
2835
+ p,
2836
+ this.plainLineEnd(p)
2837
+ )
2838
+ } else {
2839
+ this.recordComment(p)
2840
+ }
2841
+ p = yamlLineEnd(this.text, p, this.len)
2842
+ } else {
2843
+ break
2844
+ }
2845
+ }
2846
+ this.pos = p
2847
+ }
2848
+
2849
+ /** Internal: whether a `:` before `after` ends a flow plain scalar. */
2850
+ flowColonEnds(after: i32): boolean {
2851
+ const c: i32 = this.at(after)
2852
+ return c === YAML_LBRACKET || c === YAML_LBRACE || this.flowIndicatorBoundary(after)
2853
+ }
2854
+
2855
+ /** Internal: where a flow plain scalar's line from `from` stops. */
2856
+ flowPlainLineEnd(from: i32, lineStart: i32): i32 {
2857
+ let i: i32 = from
2858
+ while (i < this.len) {
2859
+ const hit: i32 = toI32(indexOfAny(this.text, YAML_PLAIN_FLOW_STOPS, i))
2860
+ if (hit < 0) {
2861
+ return this.len
2862
+ }
2863
+ const c: i32 = this.at(hit)
2864
+ if (c === YAML_COLON) {
2865
+ if (this.flowColonEnds(hit + 1)) {
2866
+ return hit
2867
+ }
2868
+ } else if (c === YAML_HASH) {
2869
+ if (hit > lineStart && yamlIsBlank(this.at(hit - 1))) {
2870
+ return hit
2871
+ }
2872
+ } else {
2873
+ return hit
2874
+ }
2875
+ i = hit + 1
2876
+ }
2877
+ return this.len
2878
+ }
2879
+
2880
+ /** Internal: reads a flow-context plain scalar at the cursor, with its continuation lines. */
2881
+ scanFlowPlain(): i32 {
2882
+ const start: i32 = this.pos
2883
+ const i: i32 = this.flowPlainLineEnd(start, start)
2884
+ let valueEnd: i32 = this.trimEnd(start, i)
2885
+ if (!yamlIsBreak(this.at(i))) {
2886
+ this.pos = i
2887
+ return this.plainNode(start, valueEnd)
2888
+ }
2889
+ let scan: i32 = yamlAfterBreak(this.text, i)
2890
+ while (true) {
2891
+ let j: i32 = scan
2892
+ while (yamlIsBlank(this.at(j))) {
2893
+ j += 1
2894
+ }
2895
+ const c: i32 = this.at(j)
2896
+ if (yamlIsBreak(c)) {
2897
+ scan = yamlAfterBreak(this.text, j)
2898
+ continue
2899
+ }
2900
+ if (c < 0 || c === YAML_COMMA || c === YAML_LBRACKET || c === YAML_RBRACKET) {
2901
+ break
2902
+ }
2903
+ if (c === YAML_LBRACE || c === YAML_RBRACE || c === YAML_HASH) {
2904
+ break
2905
+ }
2906
+ if (j === scan && this.isDocMarker(j)) {
2907
+ break
2908
+ }
2909
+ if (c === YAML_COLON && this.flowColonEnds(j + 1)) {
2910
+ break
2911
+ }
2912
+ if (!this.flowIndentReported) {
2913
+ this.checkFlowIndent(scan)
2914
+ }
2915
+ const lineEnd: i32 = this.flowPlainLineEnd(j, j)
2916
+ valueEnd = this.trimEnd(j, lineEnd)
2917
+ if (!yamlIsBreak(this.at(lineEnd))) {
2918
+ this.pos = lineEnd
2919
+ return this.plainNode(start, valueEnd)
2920
+ }
2921
+ scan = yamlAfterBreak(this.text, lineEnd)
2922
+ }
2923
+ this.pos = valueEnd
2924
+ return this.plainNode(start, valueEnd)
2925
+ }
2926
+
2927
+ /** Internal: a plain scalar node over `[start, end)`. */
2928
+ plainNode(start: i32, end: i32): i32 {
2929
+ const stream: YamlStream = this.stream
2930
+ const node: i32 = stream.newNode(YAML_SCALAR, YAML_PLAIN, start, end)
2931
+ yamlPut(stream.firsts, node, start)
2932
+ yamlPut(stream.counts, node, end)
2933
+ return node
2934
+ }
2935
+
2936
+ /** Internal: reports nesting past `maxDepth`, gives up on the rest of the text, and stands in an empty node. */
2937
+ depthLimitNode(): i32 {
2938
+ const at: i32 = this.pos
2939
+ this.error(YAML_ERR_DEPTH_LIMIT, `Exceeded maximum nesting depth of ${this.maxDepth}`, at, at)
2940
+ this.pos = this.len
2941
+ this.depthHit = true
2942
+ return this.emptyScalar(at)
2943
+ }
2944
+
2945
+ /** Internal: reads one flow node with its properties. */
2946
+ parseFlowNode(): i32 {
2947
+ this.skipFlowWs()
2948
+ const props: i32 = this.scanProps(true)
2949
+ this.skipFlowWs()
2950
+ const c: i32 = this.at(this.pos)
2951
+ let node: i32 = -1
2952
+ if (c === YAML_LBRACKET) {
2953
+ node = this.parseFlowSeq()
2954
+ } else if (c === YAML_LBRACE) {
2955
+ node = this.parseFlowMap()
2956
+ } else if (c === YAML_DQUOTE || c === YAML_SQUOTE) {
2957
+ node = this.scanQuoted(c, this.flowIndentReported ? YAML_NONE : this.flowIndent)
2958
+ } else if (c === YAML_STAR) {
2959
+ node = this.scanAlias()
2960
+ } else {
2961
+ if (this.plainStartClass(c) !== 0) {
2962
+ this.reportBadPlainStart(this.pos, true)
2963
+ }
2964
+ node = this.scanFlowPlain()
2965
+ }
2966
+ return this.attachProps(node, props)
2967
+ }
2968
+
2969
+ /** Internal: steps over a `?` explicit-key indicator in flow; whether there was one. */
2970
+ skipFlowExplicitKey(): boolean {
2971
+ if (!this.isExplicitKey(this.pos)) {
2972
+ return false
2973
+ }
2974
+ this.pos += 1
2975
+ this.skipFlowWs()
2976
+ return true
2977
+ }
2978
+
2979
+ /** Internal: reports an implicit key whose `:` is not on the key's line. */
2980
+ checkImplicitKeyLine(afterKey: i32): void {
2981
+ if (this.crossesLine(afterKey, this.pos)) {
2982
+ this.error(
2983
+ YAML_ERR_BAD_IMPLICIT_KEY,
2984
+ 'An implicit key must be followed by its ":" on the same line',
2985
+ afterKey,
2986
+ this.pos + 1
2987
+ )
2988
+ }
2989
+ }
2990
+
2991
+ /** Internal: reports an implicit key longer than YAML allows. */
2992
+ reportLongImplicitKey(keyStart: i32, colon: i32): void {
2993
+ this.error(
2994
+ YAML_ERR_BAD_IMPLICIT_KEY,
2995
+ `An implicit key may not run more than ${YAML_MAX_KEY_LENGTH} characters before its ":"`,
2996
+ keyStart,
2997
+ colon + 1
2998
+ )
2999
+ }
3000
+
3001
+ /** Internal: reports content between a key node and its `:`. */
3002
+ checkKeyGap(colon: i32): void {
3003
+ let from: i32 = this.pos
3004
+ while (yamlIsBlank(this.at(from))) {
3005
+ from += 1
3006
+ }
3007
+ if (from >= colon) {
3008
+ return
3009
+ }
3010
+ const to: i32 = this.trimEnd(from, colon)
3011
+ this.error(
3012
+ YAML_ERR_UNEXPECTED_CONTENT,
3013
+ `Unexpected "${this.text.substring(from, to)}" between a mapping key and its ":"`,
3014
+ from,
3015
+ to
3016
+ )
3017
+ }
3018
+
3019
+ /** Internal: reports node properties written before a `?` on its line. */
3020
+ reportPropsBeforeExplicitKey(at: i32): void {
3021
+ this.error(
3022
+ YAML_ERR_BAD_PROPERTY,
3023
+ 'Node properties cannot come before a "?" explicit key indicator on its line; write them after the "?", or on a line of their own above',
3024
+ at,
3025
+ at + 1
3026
+ )
3027
+ }
3028
+
3029
+ /** Internal: steps over a `,` with no entry before it, reporting it; whether there was one. */
3030
+ skipEmptyFlowEntry(): boolean {
3031
+ if (this.at(this.pos) !== YAML_COMMA) {
3032
+ return false
3033
+ }
3034
+ this.error(YAML_ERR_UNEXPECTED_COMMA, 'Missing value before ","', this.pos, this.pos + 1)
3035
+ this.pos += 1
3036
+ return true
3037
+ }
3038
+
3039
+ /** Internal: reads the flow value after a `:` in flow, or `-1` when the entry ends first. */
3040
+ parseFlowValue(close: i32): i32 {
3041
+ this.pos += 1
3042
+ this.skipFlowWs()
3043
+ const c: i32 = this.at(this.pos)
3044
+ if (c < 0 || c === YAML_COMMA || c === close) {
3045
+ return -1
3046
+ }
3047
+ return this.parseFlowNode()
3048
+ }
3049
+
3050
+ /** Internal: reads the separator after a flow entry; whether the collection goes on. */
3051
+ flowSeparator(close: i32, start: i32): boolean {
3052
+ const c: i32 = this.at(this.pos)
3053
+ if (c === YAML_COMMA) {
3054
+ this.pos += 1
3055
+ return true
3056
+ }
3057
+ if (c === close) {
3058
+ this.pos += 1
3059
+ return false
3060
+ }
3061
+ this.reportUnterminatedFlow(close, start)
3062
+ return false
3063
+ }
3064
+
3065
+ /** Internal: reports a flow collection opened at `start` that never closes. */
3066
+ reportUnterminatedFlow(close: i32, start: i32): void {
3067
+ if (this.depthHit) {
3068
+ return
3069
+ }
3070
+ this.error(
3071
+ YAML_ERR_UNTERMINATED_FLOW,
3072
+ close === YAML_RBRACKET
3073
+ ? 'Missing closing "]" for flow sequence'
3074
+ : 'Missing closing "}" for flow mapping',
3075
+ start,
3076
+ this.pos
3077
+ )
3078
+ }
3079
+
3080
+ /** Internal: reads the flow sequence opening at the cursor. */
3081
+ parseFlowSeq(): i32 {
3082
+ if (this.depth >= this.maxDepth) {
3083
+ return this.depthLimitNode()
3084
+ }
3085
+ this.depth += 1
3086
+ const node: i32 = this.parseFlowSeqBody()
3087
+ this.depth -= 1
3088
+ return node
3089
+ }
3090
+
3091
+ /** Internal: the body of `parseFlowSeq`, inside its depth guard. */
3092
+ parseFlowSeqBody(): i32 {
3093
+ const start: i32 = this.pos
3094
+ const base: i32 = this.scratchTop
3095
+ this.pos += 1
3096
+ while (true) {
3097
+ this.skipFlowWs()
3098
+ const c: i32 = this.at(this.pos)
3099
+ if (c === YAML_RBRACKET) {
3100
+ this.pos += 1
3101
+ break
3102
+ }
3103
+ if (c < 0) {
3104
+ this.reportUnterminatedFlow(YAML_RBRACKET, start)
3105
+ break
3106
+ }
3107
+ if (this.skipEmptyFlowEntry()) {
3108
+ continue
3109
+ }
3110
+ const explicit: boolean = this.skipFlowExplicitKey()
3111
+ let item: i32 = this.parseFlowNode()
3112
+ const afterItem: i32 = this.pos
3113
+ this.skipFlowWs()
3114
+ if (this.at(this.pos) === YAML_COLON) {
3115
+ if (!explicit) {
3116
+ this.checkImplicitKeyLine(afterItem)
3117
+ }
3118
+ const value: i32 = this.parseFlowValue(YAML_RBRACKET)
3119
+ item = this.singlePairMap(this.makePair(item, value))
3120
+ this.skipFlowWs()
3121
+ } else if (explicit) {
3122
+ item = this.singlePairMap(this.makePair(item, YAML_NONE))
3123
+ }
3124
+ this.scratchTop = yamlStackPush(this.scratch, this.scratchTop, item)
3125
+ if (!this.flowSeparator(YAML_RBRACKET, start)) {
3126
+ break
3127
+ }
3128
+ }
3129
+ const node: i32 = this.stream.newNode(YAML_SEQ, YAML_FLOW, start, this.pos)
3130
+ this.finishCollection(node, base)
3131
+ return node
3132
+ }
3133
+
3134
+ /** Internal: the flow map of the one pair a `k: v` entry of a flow sequence makes. */
3135
+ singlePairMap(pair: i32): i32 {
3136
+ const stream: YamlStream = this.stream
3137
+ const node: i32 = stream.newNode(YAML_MAP, YAML_FLOW, stream.start(pair), stream.end(pair))
3138
+ yamlPut(stream.firsts, node, stream.pushKid(pair))
3139
+ yamlPut(stream.counts, node, 1)
3140
+ return node
3141
+ }
3142
+
3143
+ /** Internal: reads the flow mapping opening at the cursor. */
3144
+ parseFlowMap(): i32 {
3145
+ if (this.depth >= this.maxDepth) {
3146
+ return this.depthLimitNode()
3147
+ }
3148
+ this.depth += 1
3149
+ const node: i32 = this.parseFlowMapBody()
3150
+ this.depth -= 1
3151
+ return node
3152
+ }
3153
+
3154
+ /** Internal: the body of `parseFlowMap`, inside its depth guard. */
3155
+ parseFlowMapBody(): i32 {
3156
+ const start: i32 = this.pos
3157
+ const base: i32 = this.scratchTop
3158
+ let keySet: i32 = -1
3159
+ this.pos += 1
3160
+ while (true) {
3161
+ this.skipFlowWs()
3162
+ const c: i32 = this.at(this.pos)
3163
+ if (c === YAML_RBRACE) {
3164
+ this.pos += 1
3165
+ break
3166
+ }
3167
+ if (c < 0) {
3168
+ this.reportUnterminatedFlow(YAML_RBRACE, start)
3169
+ break
3170
+ }
3171
+ if (this.skipEmptyFlowEntry()) {
3172
+ continue
3173
+ }
3174
+ this.skipFlowExplicitKey()
3175
+ const key: i32 = this.parseFlowNode()
3176
+ this.skipFlowWs()
3177
+ let value: i32 = -1
3178
+ if (this.at(this.pos) === YAML_COLON) {
3179
+ value = this.parseFlowValue(YAML_RBRACE)
3180
+ }
3181
+ keySet = this.trackKey(base, key, keySet)
3182
+ this.scratchTop = yamlStackPush(this.scratch, this.scratchTop, this.makePair(key, value))
3183
+ this.skipFlowWs()
3184
+ if (!this.flowSeparator(YAML_RBRACE, start)) {
3185
+ break
3186
+ }
3187
+ }
3188
+ this.releaseKeySet(keySet)
3189
+ const node: i32 = this.stream.newNode(YAML_MAP, YAML_FLOW, start, this.pos)
3190
+ this.finishCollection(node, base)
3191
+ return node
3192
+ }
3193
+
3194
+ // ---- Collections and pairs ------------------------------------------------------
3195
+
3196
+ /** Internal: moves the scratch slots from `base` up into `node`'s children. */
3197
+ finishCollection(node: i32, base: i32): void {
3198
+ const stream: YamlStream = this.stream
3199
+ let first: i32 = stream.kidTop
3200
+ let i: i32 = base
3201
+ while (i < this.scratchTop) {
3202
+ const slot: i32 = stream.pushKid(yamlAt(this.scratch, i))
3203
+ if (i === base) {
3204
+ first = slot
3205
+ }
3206
+ i += 1
3207
+ }
3208
+ yamlPut(stream.firsts, node, first)
3209
+ yamlPut(stream.counts, node, this.scratchTop - base)
3210
+ this.scratchTop = base
3211
+ }
3212
+
3213
+ /** Internal: a pair node joining `key` to `value`, which is `-1` when the key has none. */
3214
+ makePair(key: i32, value: i32): i32 {
3215
+ const stream: YamlStream = this.stream
3216
+ const end: i32 = value >= 0 ? stream.end(value) : stream.end(key)
3217
+ const pair: i32 = stream.newNode(YAML_PAIR, YAML_PLAIN, stream.start(key), end)
3218
+ yamlPut(stream.firsts, pair, key)
3219
+ yamlPut(stream.counts, pair, value)
3220
+ if (this.isMergeKey(key)) {
3221
+ this.checkMerge(key, value)
3222
+ }
3223
+ return pair
3224
+ }
3225
+
3226
+ /**
3227
+ * Internal: reports the value of merge key `key` unless it is a map, an alias
3228
+ * to one or a sequence of those; an empty value is reported on the key, a
3229
+ * sequence on each item that is not, and an alias to a sequence on the alias.
3230
+ * An alias with no target is already reported.
3231
+ */
3232
+ checkMerge(key: i32, value: i32): void {
3233
+ const stream: YamlStream = this.stream
3234
+ if (value < 0) {
3235
+ this.badMerge(stream.start(key), stream.end(key))
3236
+ return
3237
+ }
3238
+ const onAlias: boolean = stream.kind(value) === YAML_ALIAS
3239
+ const seq: i32 = onAlias ? stream.target(value) : value
3240
+ if (seq >= 0 && stream.kind(seq) === YAML_SEQ) {
3241
+ const count: i32 = stream.count(seq)
3242
+ let i: i32 = 0
3243
+ while (i < count) {
3244
+ const item: i32 = stream.child(seq, i)
3245
+ if (!this.mergesMap(item)) {
3246
+ if (onAlias) {
3247
+ this.badMerge(stream.start(value), stream.end(value))
3248
+ return
3249
+ }
3250
+ this.badMerge(stream.start(item), stream.end(item))
3251
+ }
3252
+ i += 1
3253
+ }
3254
+ } else if (!this.mergesMap(value)) {
3255
+ this.badMerge(stream.start(value), stream.end(value))
3256
+ }
3257
+ }
3258
+
3259
+ /** Internal: whether `n` is a map, an alias to one or an alias with no target, which merges nothing and is reported as such. */
3260
+ mergesMap(n: i32): boolean {
3261
+ const stream: YamlStream = this.stream
3262
+ if (stream.kind(n) === YAML_ALIAS) {
3263
+ const target: i32 = stream.target(n)
3264
+ return target < 0 || stream.kind(target) === YAML_MAP
3265
+ }
3266
+ return stream.kind(n) === YAML_MAP
3267
+ }
3268
+
3269
+ /** Internal: reports a merge value that names no map over `start` to `end`. */
3270
+ badMerge(start: i32, end: i32): void {
3271
+ this.error(
3272
+ YAML_ERR_BAD_MERGE,
3273
+ "Merge key value must be a map, an alias to a map or a sequence of those",
3274
+ start,
3275
+ end
3276
+ )
3277
+ }
3278
+
3279
+ /** Internal: whether `key` is a `<<` merge key, which the duplicate check skips when merging is on. */
3280
+ isMergeKey(key: i32): boolean {
3281
+ return yamlIsMergeKey(this.stream, key)
3282
+ }
3283
+
3284
+ /**
3285
+ * Internal: whether two keys share a style, a tag and their source bytes,
3286
+ * which makes them equal without decoding. The tag counts because a key's
3287
+ * range leaves out its properties: `~` is a null, `!!str ~` the string `"~"`.
3288
+ */
3289
+ sameKeyBytes(a: i32, b: i32): boolean {
3290
+ const stream: YamlStream = this.stream
3291
+ if (
3292
+ stream.kind(a) !== YAML_SCALAR ||
3293
+ stream.kind(b) !== YAML_SCALAR ||
3294
+ stream.style(a) !== stream.style(b) ||
3295
+ stream.tag(a) !== stream.tag(b)
3296
+ ) {
3297
+ return false
3298
+ }
3299
+ return yamlSameBytes(this.text, stream.start(a), stream.end(a), stream.start(b), stream.end(b))
3300
+ }
3301
+
3302
+ /**
3303
+ * Internal: reports `key` when an earlier pair of the map whose pairs start
3304
+ * at scratch slot `base` has an equal key. Up to YAML_PAIRWISE_KEYS pairs the
3305
+ * keys are compared pairwise, by bytes first; past that the map takes a hash
3306
+ * set from the pool, whose index it returns (`set` is the one it holds, or `-1`).
3307
+ */
3308
+ trackKey(base: i32, key: i32, set: i32): i32 {
3309
+ if (!this.uniqueKeys || this.isMergeKey(key)) {
3310
+ return set
3311
+ }
3312
+ const stream: YamlStream = this.stream
3313
+ if (set < 0 && this.scratchTop - base < YAML_PAIRWISE_KEYS) {
3314
+ let text: string = ""
3315
+ let decoded: boolean = false
3316
+ let i: i32 = base
3317
+ while (i < this.scratchTop) {
3318
+ const other: i32 = stream.key(yamlAt(this.scratch, i))
3319
+ let same: boolean = this.sameKeyBytes(other, key)
3320
+ if (!same && !this.isMergeKey(other)) {
3321
+ if (!decoded) {
3322
+ text = yamlKeyText(stream, key)
3323
+ decoded = true
3324
+ }
3325
+ same = yamlKeyText(stream, other) === text
3326
+ }
3327
+ if (same) {
3328
+ this.reportDuplicateKey(key)
3329
+ return set
3330
+ }
3331
+ i += 1
3332
+ }
3333
+ return set
3334
+ }
3335
+ let held: i32 = set
3336
+ if (held < 0) {
3337
+ held = this.keySetTop
3338
+ if (held >= toI32(this.keySets.length)) {
3339
+ this.keySets.push(new YamlKeySet())
3340
+ }
3341
+ this.keySetTop = held + 1
3342
+ let i: i32 = base
3343
+ while (i < this.scratchTop) {
3344
+ const other: i32 = stream.key(yamlAt(this.scratch, i))
3345
+ if (!this.isMergeKey(other)) {
3346
+ this.keySets[held].add(yamlKeyText(stream, other))
3347
+ }
3348
+ i += 1
3349
+ }
3350
+ }
3351
+ if (!this.keySets[held].add(yamlKeyText(stream, key))) {
3352
+ this.reportDuplicateKey(key)
3353
+ }
3354
+ return held
3355
+ }
3356
+
3357
+ /** Internal: hands the key set a finished map held back to the pool. */
3358
+ releaseKeySet(set: i32): void {
3359
+ if (set >= 0) {
3360
+ this.keySets[set].clear()
3361
+ this.keySetTop = set
3362
+ }
3363
+ }
3364
+
3365
+ /** Internal: reports `key` as a repeat of an earlier key in its map. */
3366
+ reportDuplicateKey(key: i32): void {
3367
+ const stream: YamlStream = this.stream
3368
+ this.error(
3369
+ YAML_ERR_DUPLICATE_KEY,
3370
+ `Map key "${yamlKeyText(stream, key)}" is duplicated`,
3371
+ stream.start(key),
3372
+ stream.end(key)
3373
+ )
3374
+ }
3375
+
3376
+ // ---- Documents --------------------------------------------------------------------
3377
+
3378
+ /** Internal: the start of a node written on the `---` line at `marker`, or `-1` when the line ends first. */
3379
+ docMarkerInlineNode(marker: i32): i32 {
3380
+ let i: i32 = marker + 3
3381
+ while (yamlIsBlank(this.at(i))) {
3382
+ i += 1
3383
+ }
3384
+ const c: i32 = this.at(i)
3385
+ if (c < 0 || yamlIsBreak(c) || c === YAML_HASH) {
3386
+ if (c === YAML_HASH && this.keepComments) {
3387
+ this.recordComment(i)
3388
+ }
3389
+ this.pos = this.nextLineStart(i)
3390
+ return -1
3391
+ }
3392
+ this.pos = i
3393
+ return i
3394
+ }
3395
+
3396
+ /** Internal: whether the content at `from` opens a block mapping: a `?` key, or an implicit key's `:`. */
3397
+ opensBlockMapping(from: i32): boolean {
3398
+ const p: i32 = this.skipPropsAhead(from)
3399
+ return this.isExplicitKey(p) || this.findKeyColon(p) >= 0
3400
+ }
3401
+
3402
+ /** Internal: reports content after the `...` marker at `marker` on its line. */
3403
+ checkDocEndTail(marker: i32): void {
3404
+ if (this.lineRestIsBlank(marker + 3)) {
3405
+ return
3406
+ }
3407
+ let after: i32 = marker + 3
3408
+ while (yamlIsBlank(this.at(after))) {
3409
+ after += 1
3410
+ }
3411
+ this.error(
3412
+ YAML_ERR_UNEXPECTED_CONTENT,
3413
+ 'Unexpected content after the "..." document-end marker',
3414
+ after,
3415
+ this.plainLineEnd(after)
3416
+ )
3417
+ }
3418
+
3419
+ /** Internal: whether every line from `from` is blank, a comment, or a bare document marker. */
3420
+ onlyEmptyDocumentsFollow(from: i32): boolean {
3421
+ let i: i32 = from
3422
+ while (i < this.len) {
3423
+ if (!this.lineRestIsBlank(i)) {
3424
+ if (!this.isDocMarker(i) || !this.lineRestIsBlank(i + 3)) {
3425
+ return false
3426
+ }
3427
+ }
3428
+ i = this.nextLineStart(i)
3429
+ }
3430
+ return true
3431
+ }
3432
+
3433
+ /** Internal: reads the document that starts at the `---` line's content at `contentPos`. */
3434
+ parseDocMarkerNode(contentPos: i32): i32 {
3435
+ const seq: boolean = this.isSeqEntryDash(contentPos)
3436
+ if (seq || this.opensBlockMapping(contentPos)) {
3437
+ this.error(
3438
+ YAML_ERR_UNEXPECTED_CONTENT,
3439
+ seq
3440
+ ? 'A block sequence cannot start on the "---" line'
3441
+ : 'A block mapping cannot start on the "---" line',
3442
+ contentPos,
3443
+ this.plainLineEnd(contentPos)
3444
+ )
3445
+ }
3446
+ this.pos = contentPos
3447
+ return this.parseNode(toI32(0), YAML_NONE, true)
3448
+ }
3449
+
3450
+ /** Internal: reads a document's root from the line `peekLine` found, whose content is at `contentPos`. */
3451
+ parseBareRoot(contentPos: i32, indent: i32): i32 {
3452
+ if (indent !== 0) {
3453
+ this.checkRootTabIndent(contentPos)
3454
+ }
3455
+ this.pos = contentPos
3456
+ return this.parseNode(indent, YAML_NONE, true)
3457
+ }
3458
+
3459
+ /** Internal: reports a tab in the indentation of a root collection's first line, which starts at the cursor. */
3460
+ checkRootTabIndent(contentPos: i32): void {
3461
+ const lineStart: i32 = this.pos
3462
+ let tab: i32 = lineStart
3463
+ while (tab < contentPos && this.at(tab) !== YAML_TAB) {
3464
+ tab += 1
3465
+ }
3466
+ if (tab === contentPos || this.tabReportedAt === lineStart) {
3467
+ return
3468
+ }
3469
+ if (this.isSeqEntryDash(contentPos) || this.opensBlockMapping(contentPos)) {
3470
+ this.error(YAML_ERR_TAB_INDENT, "Tabs cannot be used for indentation", tab, contentPos)
3471
+ this.tabReportedAt = lineStart
3472
+ }
3473
+ }
3474
+
3475
+ /** Internal: whether the line `peekLine` found is a `---` or `...` marker. */
3476
+ atMarkerLine(): boolean {
3477
+ return !this.lineEof && this.lineIndent === 0 && this.isDocMarker(this.lineContent)
3478
+ }
3479
+
3480
+ /** Internal: warns when the core-schema tag of the properties in `slot` does not fit `node`. */
3481
+ checkTaggedNode(node: i32, slot: i32): void {
3482
+ const stream: YamlStream = this.stream
3483
+ const tag: string = stream.tag(node)
3484
+ let want: i32 = 0
3485
+ if (tag === "str" || tag === "int" || tag === "float" || tag === "bool" || tag === "null") {
3486
+ want = YAML_SCALAR
3487
+ } else if (tag === "binary" || tag === "timestamp") {
3488
+ want = YAML_SCALAR
3489
+ } else if (tag === "map" || tag === "set") {
3490
+ want = YAML_MAP
3491
+ } else if (tag === "seq" || tag === "omap" || tag === "pairs") {
3492
+ want = YAML_SEQ
3493
+ } else {
3494
+ return
3495
+ }
3496
+ let start: i32 = stream.start(node)
3497
+ let end: i32 = stream.end(node)
3498
+ if (start === end) {
3499
+ start = yamlAt(stream.propTagStarts, slot)
3500
+ end = yamlAt(stream.propTagEnds, slot)
3501
+ }
3502
+ if (stream.kind(node) !== want) {
3503
+ let what: string = "a sequence"
3504
+ if (want === YAML_SCALAR) {
3505
+ what = "a scalar"
3506
+ } else if (want === YAML_MAP) {
3507
+ what = "a mapping"
3508
+ }
3509
+ this.warn(YAML_ERR_BAD_TAG_VALUE, `!!${tag} describes ${what}, and cannot be applied here`, start, end)
3510
+ return
3511
+ }
3512
+ if (tag === "int" || tag === "float" || tag === "bool" || tag === "null") {
3513
+ if (stream.type(node) === YAML_STRING) {
3514
+ this.warn(
3515
+ YAML_ERR_BAD_TAG_VALUE,
3516
+ `"${stream.text(node)}" is not a valid !!${tag} value; it is kept as a string`,
3517
+ start,
3518
+ end
3519
+ )
3520
+ }
3521
+ }
3522
+ }
3523
+
3524
+ /**
3525
+ * Internal: checks what follows the first document's node, and answers the
3526
+ * end of its `...` footer when one closes it, or `-1`.
3527
+ */
3528
+ checkDocumentEnd(): i32 {
3529
+ if (this.pos >= this.len) {
3530
+ return -1
3531
+ }
3532
+ this.finishLineIfMidLine()
3533
+ this.peekLine(toI32(0))
3534
+ if (this.lineEof) {
3535
+ return -1
3536
+ }
3537
+ if (this.atMarkerLine()) {
3538
+ return this.warnIfMoreDocuments(this.lineContent)
3539
+ }
3540
+ this.error(
3541
+ YAML_ERR_UNEXPECTED_CONTENT,
3542
+ 'Unexpected content after the document node — start a new document with "---"',
3543
+ this.lineContent,
3544
+ this.plainLineEnd(this.lineContent)
3545
+ )
3546
+ return -1
3547
+ }
3548
+
3549
+ /**
3550
+ * Internal: warns that the documents after the marker at `marker` were not
3551
+ * read, unless none of them holds anything. Answers the end of a `...`
3552
+ * marker, which is the first document's footer, or `-1`.
3553
+ */
3554
+ warnIfMoreDocuments(marker: i32): i32 {
3555
+ this.recordLineComment(marker + 3)
3556
+ const footer: boolean = this.at(marker) === YAML_DOT
3557
+ let hidden: boolean = false
3558
+ if (footer) {
3559
+ this.checkDocEndTail(marker)
3560
+ } else {
3561
+ hidden = !this.lineRestIsBlank(marker + 3)
3562
+ }
3563
+ this.pos = this.nextLineStart(marker + 3)
3564
+ const footerEnd: i32 = footer ? marker + 3 : -1
3565
+ if (!hidden) {
3566
+ this.peekLine(toI32(0))
3567
+ if (this.lineEof || this.onlyEmptyDocumentsFollow(this.pos)) {
3568
+ return footerEnd
3569
+ }
3570
+ }
3571
+ this.warn(
3572
+ YAML_ERR_MULTIPLE_DOCUMENTS,
3573
+ "Only the first document of this stream was parsed — turn off firstDocumentOnly to read the rest",
3574
+ marker,
3575
+ marker + 3
3576
+ )
3577
+ return footerEnd
3578
+ }
3579
+
3580
+ /**
3581
+ * Internal: reads the directives and markers before a document's body, and
3582
+ * answers which of `YAML_SAW_*` it met. `closed` says the document before
3583
+ * ended with `...` (or there is none), so a directive may follow.
3584
+ */
3585
+ skipStreamHead(closed: boolean): i32 {
3586
+ let seen: i32 = 0
3587
+ let sawDirective: boolean = false
3588
+ while (true) {
3589
+ this.peekLine(toI32(0))
3590
+ if (this.lineEof) {
3591
+ break
3592
+ }
3593
+ const p: i32 = this.lineContent
3594
+ const c: i32 = this.at(p)
3595
+ if (c === YAML_PERCENT && this.lineIndent === 0) {
3596
+ if (!closed && (seen & YAML_SAW_DOC_END) === 0) {
3597
+ this.error(
3598
+ YAML_ERR_UNEXPECTED_DIRECTIVE,
3599
+ 'A directive must be preceded by a "..." document-end marker',
3600
+ p,
3601
+ this.tokenEnd(p)
3602
+ )
3603
+ }
3604
+ this.readDirective(p)
3605
+ sawDirective = true
3606
+ this.recordLineComment(p)
3607
+ this.pos = this.nextLineStart(p)
3608
+ continue
3609
+ }
3610
+ if (!this.atMarkerLine()) {
3611
+ break
3612
+ }
3613
+ if (c === YAML_DOT) {
3614
+ this.checkDocEndTail(p)
3615
+ this.recordLineComment(p + 3)
3616
+ this.pos = this.nextLineStart(p + 3)
3617
+ seen = seen | YAML_SAW_DOC_END
3618
+ continue
3619
+ }
3620
+ this.markerAt = p
3621
+ const inline: i32 = this.docMarkerInlineNode(p)
3622
+ return inline >= 0 ? seen | YAML_SAW_DOC_START | YAML_SAW_INLINE_NODE : seen | YAML_SAW_DOC_START
3623
+ }
3624
+ if (sawDirective) {
3625
+ this.error(
3626
+ YAML_ERR_UNEXPECTED_DIRECTIVE,
3627
+ 'A directive must be followed by a "---" document start',
3628
+ this.pos,
3629
+ this.pos
3630
+ )
3631
+ }
3632
+ return seen
3633
+ }
3634
+
3635
+ /** Internal: steps past a UTF-8 byte order mark. */
3636
+ skipBom(): void {
3637
+ if (
3638
+ yamlByte(this.text, 0) === 0xef &&
3639
+ yamlByte(this.text, 1) === 0xbb &&
3640
+ yamlByte(this.text, 2) === 0xbf
3641
+ ) {
3642
+ this.pos = 3
3643
+ }
3644
+ }
3645
+
3646
+ /** Internal: reads the first document only. */
3647
+ parseFirst(): void {
3648
+ this.skipBom()
3649
+ const markers: i32 = this.skipStreamHead(true)
3650
+ let root: i32 = -1
3651
+ if ((markers & YAML_SAW_INLINE_NODE) !== 0) {
3652
+ root = this.parseDocMarkerNode(this.pos)
3653
+ } else {
3654
+ this.peekLine(toI32(0))
3655
+ if (!this.lineEof && !this.atMarkerLine()) {
3656
+ root = this.parseBareRoot(this.lineContent, this.lineIndent)
3657
+ }
3658
+ }
3659
+ const footerEnd: i32 = this.checkDocumentEnd()
3660
+ this.pushDocument(root, footerEnd)
3661
+ }
3662
+
3663
+ /** Internal: reads every document of the stream. */
3664
+ parseAll(): void {
3665
+ this.skipBom()
3666
+ let closed: boolean = true
3667
+ while (true) {
3668
+ this.markerAt = -1
3669
+ const markers: i32 = this.skipStreamHead(closed)
3670
+ const sawStart: boolean = (markers & YAML_SAW_DOC_START) !== 0
3671
+ let root: i32 = -1
3672
+ let body: boolean = false
3673
+ if ((markers & YAML_SAW_INLINE_NODE) !== 0) {
3674
+ root = this.parseDocMarkerNode(this.pos)
3675
+ this.finishLineIfMidLine()
3676
+ body = true
3677
+ } else {
3678
+ this.peekLine(toI32(0))
3679
+ if (!this.lineEof && !this.atMarkerLine()) {
3680
+ const p: i32 = this.lineContent
3681
+ const count: i32 = toI32(this.stream.docRoots.length)
3682
+ if (!sawStart && !closed && (markers & YAML_SAW_DOC_END) === 0 && count > 0) {
3683
+ this.error(
3684
+ YAML_ERR_UNEXPECTED_CONTENT,
3685
+ 'Unexpected content — start a new document with "---"',
3686
+ p,
3687
+ this.plainLineEnd(p)
3688
+ )
3689
+ }
3690
+ root = this.parseBareRoot(p, this.lineIndent)
3691
+ this.finishLineIfMidLine()
3692
+ body = true
3693
+ }
3694
+ }
3695
+ if (!sawStart && !body) {
3696
+ break
3697
+ }
3698
+ let footerEnd: i32 = -1
3699
+ this.peekLine(toI32(0))
3700
+ if (this.atMarkerLine() && this.at(this.lineContent) === YAML_DOT) {
3701
+ const p: i32 = this.lineContent
3702
+ this.checkDocEndTail(p)
3703
+ this.recordLineComment(p + 3)
3704
+ this.pos = this.nextLineStart(p + 3)
3705
+ footerEnd = p + 3
3706
+ }
3707
+ this.pushDocument(root, footerEnd)
3708
+ closed = footerEnd >= 0
3709
+ this.anchorTop = 0
3710
+ this.pendingTop = 0
3711
+ this.handleNames = []
3712
+ this.handlePrefixes = []
3713
+ this.yamlDirective = false
3714
+ this.doc += 1
3715
+ }
3716
+ const stream: YamlStream = this.stream
3717
+ const count: i32 = toI32(stream.docRoots.length)
3718
+ if (count === 0) {
3719
+ if (toI32(stream.diagCodes.length) > 0 || toI32(stream.commentStarts.length) > 0) {
3720
+ this.pushDocument(YAML_NONE, YAML_NONE)
3721
+ }
3722
+ return
3723
+ }
3724
+ let i: i32 = 0
3725
+ while (i < toI32(stream.diagDocuments.length)) {
3726
+ if (stream.diagDocuments[i] >= count) {
3727
+ stream.diagDocuments[i] = count - 1
3728
+ }
3729
+ i += 1
3730
+ }
3731
+ }
3732
+
3733
+ /** Internal: records a document whose root is `root` (or `-1`), closed by a `...` ending at `footerEnd` (or `-1`). */
3734
+ pushDocument(root: i32, footerEnd: i32): void {
3735
+ const stream: YamlStream = this.stream
3736
+ let start: i32 = root >= 0 ? stream.start(root) : this.pos
3737
+ if (this.markerAt >= 0) {
3738
+ start = this.markerAt
3739
+ }
3740
+ let end: i32 = footerEnd
3741
+ if (end < 0) {
3742
+ end = root >= 0 ? stream.end(root) : start
3743
+ if (this.markerAt + 3 > end) {
3744
+ end = this.markerAt + 3
3745
+ }
3746
+ if (start > end) {
3747
+ end = start
3748
+ }
3749
+ }
3750
+ if (start > end) {
3751
+ start = end
3752
+ }
3753
+ stream.docRoots.push(root)
3754
+ stream.docStarts.push(start)
3755
+ stream.docEnds.push(end)
3756
+ }
3757
+
3758
+ // ---- Block collections ----------------------------------------------------------
3759
+
3760
+ /**
3761
+ * Internal: the value of properties written alone at the end of their line,
3762
+ * whose slot is `props`: the node on the lines below, or an empty scalar.
3763
+ */
3764
+ parsePropsOnlyValue(props: i32, parentIndent: i32, seqAtParentIndent: boolean): i32 {
3765
+ const propsEnd: i32 = this.pos
3766
+ this.finishLine()
3767
+ this.peekLine(parentIndent + 1)
3768
+ if (!this.lineEof && this.lineIndent > parentIndent) {
3769
+ this.pos = this.lineContent
3770
+ return this.attachProps(this.parseNode(this.lineIndent, parentIndent, seqAtParentIndent), props)
3771
+ }
3772
+ if (
3773
+ seqAtParentIndent &&
3774
+ !this.lineEof &&
3775
+ this.lineIndent === parentIndent &&
3776
+ this.isSeqEntryDash(this.lineContent)
3777
+ ) {
3778
+ this.pos = this.lineContent
3779
+ return this.attachProps(this.parseBlockSeq(parentIndent), props)
3780
+ }
3781
+ return this.attachProps(this.emptyScalar(propsEnd), props)
3782
+ }
3783
+
3784
+ /** Internal: the value written on a key's line after its `:`, or `-1` when there is none. */
3785
+ parseInlineValue(parentIndent: i32): i32 {
3786
+ const props: i32 = this.scanProps(false)
3787
+ this.skipInlineSpaces()
3788
+ if (this.atLineEnd()) {
3789
+ return props >= 0 ? this.parsePropsOnlyValue(props, parentIndent, true) : -1
3790
+ }
3791
+ const c: i32 = this.at(this.pos)
3792
+ let node: i32 = -1
3793
+ if (c === YAML_STAR) {
3794
+ node = this.scanAlias()
3795
+ this.checkTrailingContent()
3796
+ } else if (c === YAML_PIPE || c === YAML_GT) {
3797
+ node = this.scanBlockScalar(parentIndent)
3798
+ } else if (c === YAML_LBRACKET || c === YAML_LBRACE) {
3799
+ this.enterFlow(parentIndent)
3800
+ node = c === YAML_LBRACKET ? this.parseFlowSeq() : this.parseFlowMap()
3801
+ this.checkTrailingContent()
3802
+ } else if (c === YAML_DQUOTE || c === YAML_SQUOTE) {
3803
+ node = this.scanQuoted(c, parentIndent)
3804
+ this.checkTrailingContent()
3805
+ } else {
3806
+ if (this.plainStartClass(c) !== 0) {
3807
+ this.reportBadPlainStart(this.pos, false)
3808
+ }
3809
+ node = this.scanPlainScalar(parentIndent)
3810
+ }
3811
+ return this.attachProps(node, props)
3812
+ }
3813
+
3814
+ /** Internal: the column of `at`: its distance from the start of its line. */
3815
+ columnOf(at: i32): i32 {
3816
+ let i: i32 = at
3817
+ while (i > 0 && !yamlIsBreak(this.at(i - 1))) {
3818
+ i -= 1
3819
+ }
3820
+ return at - i
3821
+ }
3822
+
3823
+ /**
3824
+ * Internal: a block collection written on an indicator's line (`- - a`,
3825
+ * `- k: v`, `? - a`), or `-1` when the cursor holds no such collection.
3826
+ */
3827
+ parseCompactCollection(): i32 {
3828
+ const at: i32 = this.pos
3829
+ const seq: boolean = this.isSeqEntryDash(at)
3830
+ const keyAt: i32 = seq ? at : this.skipPropsAhead(at)
3831
+ const explicit: boolean = !seq && this.isExplicitKey(keyAt)
3832
+ const colon: i32 = seq || explicit ? -1 : this.findKeyColon(keyAt)
3833
+ if (!seq && !explicit && colon < 0) {
3834
+ return -1
3835
+ }
3836
+ const column: i32 = this.columnOf(at)
3837
+ let node: i32 = -1
3838
+ if (seq) {
3839
+ node = this.parseBlockSeq(column)
3840
+ } else {
3841
+ let props: i32 = -1
3842
+ if (keyAt !== at) {
3843
+ props = this.scanProps(false)
3844
+ this.skipInlineSpaces()
3845
+ }
3846
+ if (explicit) {
3847
+ if (props >= 0) {
3848
+ this.reportPropsBeforeExplicitKey(this.pos)
3849
+ }
3850
+ node = this.attachProps(this.parseBlockMap(column, YAML_NONE, YAML_NONE, YAML_NONE), props)
3851
+ } else {
3852
+ node = this.parseBlockMap(column, colon, YAML_NONE, props)
3853
+ }
3854
+ }
3855
+ return node
3856
+ }
3857
+
3858
+ /**
3859
+ * Internal: the value after an indicator of a block collection at `indent`:
3860
+ * on the same line, or on the lines below. `compact` lets a collection
3861
+ * start on the indicator's line. `-1` when there is none.
3862
+ */
3863
+ parseValueOrChild(indent: i32, compact: boolean): i32 {
3864
+ if (compact) {
3865
+ this.skipIndicatorSeparation()
3866
+ } else {
3867
+ this.skipInlineSpaces()
3868
+ }
3869
+ if (this.atLineEnd()) {
3870
+ this.finishLine()
3871
+ this.peekLine(indent + 1)
3872
+ if (!this.lineEof && this.lineIndent > indent) {
3873
+ this.pos = this.lineContent
3874
+ return this.parseNode(this.lineIndent, indent, true)
3875
+ }
3876
+ if (!this.lineEof && this.lineIndent === indent && this.isSeqEntryDash(this.lineContent)) {
3877
+ this.pos = this.lineContent
3878
+ return this.parseBlockSeq(indent)
3879
+ }
3880
+ return -1
3881
+ }
3882
+ if (compact) {
3883
+ const collection: i32 = this.parseCompactCollection()
3884
+ if (collection >= 0) {
3885
+ return collection
3886
+ }
3887
+ }
3888
+ const node: i32 = this.parseInlineValue(indent)
3889
+ this.finishLineIfMidLine()
3890
+ return node
3891
+ }
3892
+
3893
+ /**
3894
+ * Internal: reads a block mapping at `indent`. Its first entry is already
3895
+ * found: an implicit key whose `:` is at `firstColon`, with the properties in
3896
+ * `firstProps`; or the node `firstKey`, read up to its `:`; or, with both
3897
+ * `-1`, an explicit `?` key at the cursor.
3898
+ */
3899
+ parseBlockMap(indent: i32, firstColon: i32, firstKey: i32, firstProps: i32): i32 {
3900
+ if (this.depth >= this.maxDepth) {
3901
+ return this.depthLimitNode()
3902
+ }
3903
+ this.depth += 1
3904
+ const node: i32 = this.parseBlockMapBody(indent, firstColon, firstKey, firstProps)
3905
+ this.depth -= 1
3906
+ return node
3907
+ }
3908
+
3909
+ /** Internal: the body of `parseBlockMap`, inside its depth guard. */
3910
+ parseBlockMapBody(indent: i32, firstColon: i32, firstKey: i32, firstProps: i32): i32 {
3911
+ const stream: YamlStream = this.stream
3912
+ const base: i32 = this.scratchTop
3913
+ let keySet: i32 = -1
3914
+ let contentPos: i32 = this.pos
3915
+ let firstEntry: boolean = true
3916
+ if (firstKey >= 0) {
3917
+ const firstValue: i32 = this.parseValueOrChild(indent, false)
3918
+ keySet = this.trackKey(base, firstKey, keySet)
3919
+ this.scratchTop = yamlStackPush(this.scratch, this.scratchTop, this.makePair(firstKey, firstValue))
3920
+ firstEntry = false
3921
+ }
3922
+ while (true) {
3923
+ let colon: i32 = -1
3924
+ let explicit: boolean = false
3925
+ let keyProps: i32 = -1
3926
+ if (firstEntry) {
3927
+ colon = firstColon
3928
+ explicit = firstColon < 0
3929
+ keyProps = firstProps
3930
+ } else {
3931
+ this.peekLine(indent)
3932
+ if (this.lineEof || this.lineIndent !== indent) {
3933
+ break
3934
+ }
3935
+ const lineStart: i32 = this.pos
3936
+ contentPos = this.lineContent
3937
+ if (this.isSeqEntryDash(contentPos) || (indent === 0 && this.isDocMarker(contentPos))) {
3938
+ break
3939
+ }
3940
+ const c: i32 = this.at(contentPos)
3941
+ if (c === YAML_AMP || c === YAML_BANG) {
3942
+ this.pos = contentPos
3943
+ keyProps = this.scanProps(false)
3944
+ this.skipInlineSpaces()
3945
+ contentPos = this.pos
3946
+ if (keyProps >= 0 && this.isExplicitKey(contentPos)) {
3947
+ this.reportPropsBeforeExplicitKey(contentPos)
3948
+ }
3949
+ }
3950
+ explicit = this.isExplicitKey(contentPos)
3951
+ colon = explicit ? -1 : this.findKeyColon(contentPos)
3952
+ if (colon < 0 && !explicit) {
3953
+ this.pos = lineStart
3954
+ break
3955
+ }
3956
+ }
3957
+ firstEntry = false
3958
+ let key: i32 = -1
3959
+ let value: i32 = -1
3960
+ if (explicit) {
3961
+ this.pos = contentPos + 1
3962
+ key = this.parseValueOrChild(indent, true)
3963
+ if (key < 0) {
3964
+ key = this.emptyScalar(contentPos + 1)
3965
+ }
3966
+ key = this.attachProps(key, keyProps)
3967
+ this.peekLine(indent)
3968
+ if (
3969
+ !this.lineEof &&
3970
+ this.lineIndent === indent &&
3971
+ this.at(this.lineContent) === YAML_COLON &&
3972
+ yamlIsWs(this.at(this.lineContent + 1))
3973
+ ) {
3974
+ this.pos = this.lineContent + 1
3975
+ value = this.parseValueOrChild(indent, true)
3976
+ }
3977
+ } else {
3978
+ key = this.parseImplicitKey(indent, contentPos, colon)
3979
+ key = this.attachProps(key, keyProps)
3980
+ this.pos = colon + 1
3981
+ value = this.parseValueOrChild(indent, false)
3982
+ }
3983
+ keySet = this.trackKey(base, key, keySet)
3984
+ this.scratchTop = yamlStackPush(this.scratch, this.scratchTop, this.makePair(key, value))
3985
+ }
3986
+ this.releaseKeySet(keySet)
3987
+ let start: i32 = this.pos
3988
+ let end: i32 = this.pos
3989
+ if (this.scratchTop > base) {
3990
+ start = stream.start(yamlAt(this.scratch, base))
3991
+ end = stream.end(yamlAt(this.scratch, this.scratchTop - 1))
3992
+ }
3993
+ const node: i32 = stream.newNode(YAML_MAP, YAML_BLOCK, start, end)
3994
+ this.finishCollection(node, base)
3995
+ return node
3996
+ }
3997
+
3998
+ /** Internal: reads the implicit key at `from` of a block mapping at `indent`, whose `:` is at `colon`. */
3999
+ parseImplicitKey(indent: i32, from: i32, colon: i32): i32 {
4000
+ if (colon - from > YAML_MAX_KEY_LENGTH) {
4001
+ this.reportLongImplicitKey(from, colon)
4002
+ }
4003
+ this.pos = from
4004
+ const c: i32 = this.at(from)
4005
+ let key: i32 = -1
4006
+ if (c === YAML_DQUOTE || c === YAML_SQUOTE) {
4007
+ key = this.scanQuoted(c, YAML_NONE)
4008
+ const stream: YamlStream = this.stream
4009
+ if (this.crossesLine(stream.start(key), stream.end(key))) {
4010
+ this.error(
4011
+ YAML_ERR_BAD_IMPLICIT_KEY,
4012
+ "An implicit key must be on one line",
4013
+ stream.start(key),
4014
+ stream.end(key)
4015
+ )
4016
+ }
4017
+ } else if (c === YAML_STAR) {
4018
+ key = this.scanAlias()
4019
+ } else if (c === YAML_LBRACKET || c === YAML_LBRACE) {
4020
+ this.enterFlow(indent)
4021
+ key = c === YAML_LBRACKET ? this.parseFlowSeq() : this.parseFlowMap()
4022
+ } else {
4023
+ if (this.plainStartClass(c) !== 0) {
4024
+ this.reportBadPlainStart(from, false)
4025
+ }
4026
+ return this.plainNode(from, this.trimEnd(from, colon))
4027
+ }
4028
+ if (this.pos < colon) {
4029
+ this.checkKeyGap(colon)
4030
+ }
4031
+ return key
4032
+ }
4033
+
4034
+ /** Internal: reads a block sequence at `indent`, whose first `-` is at the cursor. */
4035
+ parseBlockSeq(indent: i32): i32 {
4036
+ if (this.depth >= this.maxDepth) {
4037
+ return this.depthLimitNode()
4038
+ }
4039
+ this.depth += 1
4040
+ const node: i32 = this.parseBlockSeqBody(indent)
4041
+ this.depth -= 1
4042
+ return node
4043
+ }
4044
+
4045
+ /** Internal: the body of `parseBlockSeq`, inside its depth guard. */
4046
+ parseBlockSeqBody(indent: i32): i32 {
4047
+ const stream: YamlStream = this.stream
4048
+ const base: i32 = this.scratchTop
4049
+ let start: i32 = -1
4050
+ let contentPos: i32 = this.pos
4051
+ let firstEntry: boolean = true
4052
+ while (true) {
4053
+ if (!firstEntry) {
4054
+ this.peekLine(indent)
4055
+ if (this.lineEof || this.lineIndent !== indent) {
4056
+ break
4057
+ }
4058
+ contentPos = this.lineContent
4059
+ }
4060
+ firstEntry = false
4061
+ if (!this.isSeqEntryDash(contentPos)) {
4062
+ break
4063
+ }
4064
+ if (start < 0) {
4065
+ start = contentPos
4066
+ }
4067
+ const dash: i32 = contentPos
4068
+ this.pos = dash + 1
4069
+ this.skipIndicatorSeparation()
4070
+ let item: i32 = -1
4071
+ if (this.atLineEnd()) {
4072
+ this.finishLine()
4073
+ this.peekLine(indent + 1)
4074
+ if (!this.lineEof && this.lineIndent > indent) {
4075
+ this.pos = this.lineContent
4076
+ item = this.parseNode(this.lineIndent, indent, false)
4077
+ } else {
4078
+ item = this.emptyScalar(dash + 1)
4079
+ }
4080
+ } else {
4081
+ const c: i32 = this.at(this.pos)
4082
+ if (c === YAML_PIPE || c === YAML_GT) {
4083
+ item = this.scanBlockScalar(indent)
4084
+ } else {
4085
+ item = this.parseNode(this.pos - contentPos + indent, indent, false)
4086
+ }
4087
+ this.finishLineIfMidLine()
4088
+ }
4089
+ this.scratchTop = yamlStackPush(this.scratch, this.scratchTop, item)
4090
+ }
4091
+ if (start < 0) {
4092
+ start = this.pos
4093
+ }
4094
+ const end: i32 = this.scratchTop > base ? stream.end(yamlAt(this.scratch, this.scratchTop - 1)) : this.pos
4095
+ const node: i32 = stream.newNode(YAML_SEQ, YAML_BLOCK, start, end)
4096
+ this.finishCollection(node, base)
4097
+ return node
4098
+ }
4099
+
4100
+ /**
4101
+ * Internal: reads the block node at the cursor, at `indent`, whose parent
4102
+ * is at `parentIndent`. `seqAtParentIndent` lets a sequence at the parent's
4103
+ * own indent be the value of lone properties.
4104
+ */
4105
+ parseNode(indent: i32, parentIndent: i32, seqAtParentIndent: boolean): i32 {
4106
+ if (this.isSeqEntryDash(this.pos)) {
4107
+ return this.parseBlockSeq(indent)
4108
+ }
4109
+ const props: i32 = this.scanProps(false)
4110
+ if (props >= 0) {
4111
+ this.skipInlineSpaces()
4112
+ if (this.isSeqEntryDash(this.pos)) {
4113
+ this.error(
4114
+ YAML_ERR_UNEXPECTED_CONTENT,
4115
+ "A block sequence cannot start on the line its node properties are written on",
4116
+ this.pos,
4117
+ this.pos + 1
4118
+ )
4119
+ }
4120
+ if (this.atLineEnd()) {
4121
+ return this.parsePropsOnlyValue(props, parentIndent, seqAtParentIndent)
4122
+ }
4123
+ }
4124
+ const c: i32 = this.at(this.pos)
4125
+ if (c === YAML_STAR || c === YAML_LBRACKET || c === YAML_LBRACE) {
4126
+ let node: i32 = -1
4127
+ if (c === YAML_STAR) {
4128
+ node = this.scanAlias()
4129
+ } else {
4130
+ this.enterFlow(parentIndent)
4131
+ node = c === YAML_LBRACKET ? this.parseFlowSeq() : this.parseFlowMap()
4132
+ }
4133
+ node = this.attachProps(node, props)
4134
+ this.skipInlineSpaces()
4135
+ if (this.at(this.pos) === YAML_COLON && yamlIsWs(this.at(this.pos + 1))) {
4136
+ const stream: YamlStream = this.stream
4137
+ if (this.crossesLine(stream.start(node), stream.end(node))) {
4138
+ this.error(
4139
+ YAML_ERR_BAD_IMPLICIT_KEY,
4140
+ "An implicit key must be on one line",
4141
+ stream.start(node),
4142
+ stream.end(node)
4143
+ )
4144
+ }
4145
+ this.pos += 1
4146
+ return this.parseBlockMap(indent, YAML_NONE, node, YAML_NONE)
4147
+ }
4148
+ this.checkTrailingContent()
4149
+ return node
4150
+ }
4151
+ if (c === YAML_PIPE || c === YAML_GT) {
4152
+ return this.attachProps(this.scanBlockScalar(parentIndent), props)
4153
+ }
4154
+ if (this.isExplicitKey(this.pos)) {
4155
+ if (props >= 0) {
4156
+ this.reportPropsBeforeExplicitKey(this.pos)
4157
+ }
4158
+ return this.attachProps(this.parseBlockMap(indent, YAML_NONE, YAML_NONE, YAML_NONE), props)
4159
+ }
4160
+ const colon: i32 = this.findKeyColon(this.pos)
4161
+ if (colon >= 0) {
4162
+ return this.parseBlockMap(indent, colon, YAML_NONE, props)
4163
+ }
4164
+ if (c === YAML_DQUOTE || c === YAML_SQUOTE) {
4165
+ const quoted: i32 = this.attachProps(this.scanQuoted(c, parentIndent), props)
4166
+ this.checkTrailingContent()
4167
+ return quoted
4168
+ }
4169
+ if (this.plainStartClass(c) !== 0) {
4170
+ this.reportBadPlainStart(this.pos, false)
4171
+ }
4172
+ return this.attachProps(this.scanPlainScalar(parentIndent), props)
4173
+ }
4174
+ }
4175
+
4176
+ // ---- Key sets ---------------------------------------------------------------------
4177
+
4178
+ const YAML_FNV_OFFSET: i32 = -2128831035
4179
+ const YAML_FNV_PRIME: i32 = 16777619
4180
+ const YAML_KEY_SET_SLOTS: i32 = 64
4181
+
4182
+ /** FNV-1a over the characters of `text`. */
4183
+ const yamlKeyHash = (text: string): i32 => {
4184
+ let hash: i32 = YAML_FNV_OFFSET
4185
+ const length: i32 = toI32(text.length)
4186
+ let i: i32 = 0
4187
+ while (i < length) {
4188
+ hash = wrappingMul(hash ^ toI32(text.charCodeAt(i)), YAML_FNV_PRIME)
4189
+ i += 1
4190
+ }
4191
+ return hash
4192
+ }
4193
+
4194
+ /**
4195
+ * The keys of one large map, for the duplicate check: open addressing over
4196
+ * a power-of-two table, kept at most half full. `filled` lists the used
4197
+ * slots, so a set handed back to the pool clears only those.
4198
+ */
4199
+ class YamlKeySet {
4200
+ texts: string[]
4201
+ used: boolean[]
4202
+ filled: i32[]
4203
+ mask: i32 = 0
4204
+
4205
+ constructor() {
4206
+ this.texts = []
4207
+ this.used = []
4208
+ this.filled = []
4209
+ this.resize(YAML_KEY_SET_SLOTS)
4210
+ }
4211
+
4212
+ /** Adds `text`; false when it was already there. */
4213
+ add(text: string): boolean {
4214
+ if ((toI32(this.filled.length) + 1) * 2 > this.mask + 1) {
4215
+ this.grow()
4216
+ }
4217
+ let slot: i32 = yamlKeyHash(text) & this.mask
4218
+ while (slot < toI32(this.used.length) && this.used[slot]) {
4219
+ if (this.texts[slot] === text) {
4220
+ return false
4221
+ }
4222
+ slot = (slot + 1) & this.mask
4223
+ }
4224
+ if (slot < toI32(this.used.length)) {
4225
+ this.used[slot] = true
4226
+ this.texts[slot] = text
4227
+ }
4228
+ this.filled.push(slot)
4229
+ return true
4230
+ }
4231
+
4232
+ /** Empties the set, keeping its table. */
4233
+ clear(): void {
4234
+ let i: i32 = 0
4235
+ while (i < toI32(this.filled.length)) {
4236
+ const slot: i32 = this.filled[i]
4237
+ if (slot >= 0 && slot < toI32(this.used.length)) {
4238
+ this.used[slot] = false
4239
+ this.texts[slot] = ""
4240
+ }
4241
+ i += 1
4242
+ }
4243
+ this.filled = []
4244
+ }
4245
+
4246
+ /** Internal: doubles the table and puts every key back. */
4247
+ grow(): void {
4248
+ const texts: string[] = []
4249
+ let i: i32 = 0
4250
+ while (i < toI32(this.filled.length)) {
4251
+ const slot: i32 = this.filled[i]
4252
+ texts.push(slot >= 0 && slot < toI32(this.texts.length) ? this.texts[slot] : "")
4253
+ i += 1
4254
+ }
4255
+ this.resize((this.mask + 1) * 2)
4256
+ let j: i32 = 0
4257
+ while (j < toI32(texts.length)) {
4258
+ this.add(texts[j])
4259
+ j += 1
4260
+ }
4261
+ }
4262
+
4263
+ /** Internal: an empty table of `slots` slots. */
4264
+ resize(slots: i32): void {
4265
+ this.texts = []
4266
+ this.used = []
4267
+ this.filled = []
4268
+ let i: i32 = 0
4269
+ while (i < slots) {
4270
+ this.texts.push("")
4271
+ this.used.push(false)
4272
+ i += 1
4273
+ }
4274
+ this.mask = slots - 1
4275
+ }
4276
+ }
4277
+
4278
+ /** Whether map key `key` of `stream` is a merge key: a plain `<<`, untagged or `!!merge`, while the stream merges. */
4279
+ const yamlIsMergeKey = (stream: YamlStream, key: i32): boolean => {
4280
+ if (!stream.merge || key < 0) {
4281
+ return false
4282
+ }
4283
+ const start: i32 = stream.start(key)
4284
+ if (
4285
+ stream.end(key) !== start + 2 ||
4286
+ yamlByte(stream.source, start) !== YAML_LT ||
4287
+ yamlByte(stream.source, start + 1) !== YAML_LT ||
4288
+ stream.kind(key) !== YAML_SCALAR ||
4289
+ stream.style(key) !== YAML_PLAIN
4290
+ ) {
4291
+ return false
4292
+ }
4293
+ const tag: string = stream.tag(key)
4294
+ return tag === "" || tag === "merge"
4295
+ }
4296
+
4297
+ // ---- Key text -------------------------------------------------------------------
4298
+
4299
+ /** How many bytes `keyText` writes for a collection key before it cuts the rest to `…`. */
4300
+ const YAML_KEY_TEXT_BUDGET: i32 = 4096
4301
+ /** How deep `keyText` follows a collection key's nesting before it cuts the rest to `…`. */
4302
+ const YAML_KEY_TEXT_DEPTH: i32 = 64
4303
+ /** How many aliases in a row a path step follows before it stops. */
4304
+ const YAML_ALIAS_HOPS: i32 = 100
4305
+
4306
+ /** Writes a collection key as one line of flow text, within `YAML_KEY_TEXT_BUDGET` bytes. */
4307
+ class YamlFlowWriter {
4308
+ stream: YamlStream
4309
+ out: string
4310
+ left: i32 = 0
4311
+ cut: boolean = false
4312
+
4313
+ constructor(stream: YamlStream) {
4314
+ this.stream = stream
4315
+ this.out = ""
4316
+ this.left = YAML_KEY_TEXT_BUDGET
4317
+ }
4318
+
4319
+ /** Internal: appends `s`, or as much of it as the budget allows and then `…`. */
4320
+ append(s: string): void {
4321
+ if (this.cut) {
4322
+ return
4323
+ }
4324
+ const length: i32 = toI32(s.length)
4325
+ if (length > this.left) {
4326
+ // Back up to the start of a character, so the cut never splits one's bytes.
4327
+ let keep: i32 = this.left
4328
+ while (keep > 0 && (yamlByte(s, keep) & 0xc0) === 0x80) {
4329
+ keep -= 1
4330
+ }
4331
+ this.out = this.out + s.substring(0, keep) + "…"
4332
+ this.cut = true
4333
+ return
4334
+ }
4335
+ this.out = this.out + s
4336
+ this.left -= length
4337
+ }
4338
+
4339
+ /** Internal: appends node `n` (`-1` for a missing value), nested `depth` deep. */
4340
+ write(n: i32, depth: i32): void {
4341
+ const stream: YamlStream = this.stream
4342
+ if (depth > YAML_KEY_TEXT_DEPTH) {
4343
+ this.append("…")
4344
+ this.cut = true
4345
+ return
4346
+ }
4347
+ const at: i32 = n >= 0 ? stream.resolved(n) : -1
4348
+ if (at < 0) {
4349
+ this.append(n >= 0 ? yamlKeyText(stream, n) : "null")
4350
+ return
4351
+ }
4352
+ const kind: i32 = stream.kind(at)
4353
+ if (kind === YAML_SCALAR) {
4354
+ this.append(stream.type(at) === YAML_NULL ? "null" : stream.text(at))
4355
+ return
4356
+ }
4357
+ const count: i32 = stream.count(at)
4358
+ const map: boolean = kind === YAML_MAP
4359
+ if (count === 0) {
4360
+ this.append(map ? "{}" : "[]")
4361
+ return
4362
+ }
4363
+ this.append(map ? "{ " : "[ ")
4364
+ let i: i32 = 0
4365
+ while (i < count && !this.cut) {
4366
+ if (i > 0) {
4367
+ this.append(", ")
4368
+ }
4369
+ const item: i32 = stream.child(at, i)
4370
+ if (map) {
4371
+ this.write(stream.key(item), depth + 1)
4372
+ this.append(": ")
4373
+ this.write(stream.value(item), depth + 1)
4374
+ } else {
4375
+ this.write(item, depth + 1)
4376
+ }
4377
+ i += 1
4378
+ }
4379
+ this.append(map ? " }" : " ]")
4380
+ }
4381
+ }
4382
+
4383
+ /**
4384
+ * The text a map key is matched and reported by: a scalar's decoded text (its
4385
+ * source text for a number or a boolean, `""` for a null), an alias's target's
4386
+ * text (`*name` when it names no anchor), and a collection written as one line
4387
+ * of flow text, `{ a: 1 }` or `[ a, b ]`, cut to `…` past 4096 bytes, at a
4388
+ * character's start.
4389
+ */
4390
+ const yamlKeyText = (stream: YamlStream, node: i32): string => {
4391
+ const kind: i32 = stream.kind(node)
4392
+ if (kind === YAML_ALIAS && stream.target(node) < 0) {
4393
+ return stream.source.substring(stream.start(node), stream.end(node))
4394
+ }
4395
+ const at: i32 = stream.resolved(node)
4396
+ if (stream.kind(at) === YAML_SCALAR) {
4397
+ return stream.type(at) === YAML_NULL ? "" : stream.text(at)
4398
+ }
4399
+ const writer: YamlFlowWriter = new YamlFlowWriter(stream)
4400
+ writer.write(at, toI32(0))
4401
+ return writer.out
4402
+ }
4403
+
4404
+ /** The text map key `node` of `stream` is matched by in a path and named by in a diagnostic; see `yamlKeyText`. */
4405
+ export const keyText = (stream: YamlStream, node: i32): string => yamlKeyText(stream, node)
4406
+
4407
+ // ---- Paths ----------------------------------------------------------------------
4408
+
4409
+ /** Walks a path of map keys and sequence indexes down from a node. */
4410
+ class YamlPathWalker {
4411
+ stream: YamlStream
4412
+ /** The deepest node reached. */
4413
+ node: i32 = -1
4414
+ /** The last pair stepped through, or `-1`. */
4415
+ pair: i32 = -1
4416
+ /** How many steps matched. */
4417
+ matched: i32 = 0
4418
+
4419
+ constructor(stream: YamlStream) {
4420
+ this.stream = stream
4421
+ }
4422
+
4423
+ /** Internal: node `n` with its aliases followed, up to `YAML_ALIAS_HOPS` of them; `-1` past that. */
4424
+ follow(n: i32): i32 {
4425
+ const stream: YamlStream = this.stream
4426
+ let at: i32 = n
4427
+ let hops: i32 = 0
4428
+ while (at >= 0 && stream.kind(at) === YAML_ALIAS) {
4429
+ if (hops === YAML_ALIAS_HOPS) {
4430
+ return -1
4431
+ }
4432
+ at = stream.target(at)
4433
+ hops += 1
4434
+ }
4435
+ return at
4436
+ }
4437
+
4438
+ /**
4439
+ * Internal: whether map key `key` is matched by `step`. A one-line plain key
4440
+ * that is not a null is its own source text, so it is compared in place;
4441
+ * every other key goes through `yamlKeyText`.
4442
+ */
4443
+ keyIs(key: i32, step: string): boolean {
4444
+ const stream: YamlStream = this.stream
4445
+ const start: i32 = stream.start(key)
4446
+ const end: i32 = stream.end(key)
4447
+ if (
4448
+ stream.kind(key) === YAML_SCALAR &&
4449
+ stream.style(key) === YAML_PLAIN &&
4450
+ stream.tag(key) === "" &&
4451
+ yamlLineEnd(stream.source, start, end) === end &&
4452
+ stream.type(key) !== YAML_NULL
4453
+ ) {
4454
+ return yamlIsWord(stream.source, start, end, step)
4455
+ }
4456
+ return yamlKeyText(stream, key) === step
4457
+ }
4458
+
4459
+ /** Internal: the index `step` writes, or `-1` when it is not 1 to 9 decimal digits without a leading zero. */
4460
+ indexOf(step: string): i32 {
4461
+ const length: i32 = toI32(step.length)
4462
+ if (length === 0 || length > 9 || (length > 1 && toI32(step.charCodeAt(0)) === 48)) {
4463
+ return -1
4464
+ }
4465
+ let index: i32 = 0
4466
+ let i: i32 = 0
4467
+ while (i < length) {
4468
+ const c: i32 = toI32(step.charCodeAt(i))
4469
+ if (c < 48 || c > 57) {
4470
+ return -1
4471
+ }
4472
+ index = index * 10 + (c - 48)
4473
+ i += 1
4474
+ }
4475
+ return index
4476
+ }
4477
+
4478
+ /** Internal: the last pair of map `map` whose key `step` matches, as when a duplicate is let through; `-1` for none. */
4479
+ ownPair(map: i32, step: string): i32 {
4480
+ const stream: YamlStream = this.stream
4481
+ let i: i32 = stream.count(map) - 1
4482
+ while (i >= 0) {
4483
+ const pair: i32 = stream.child(map, i)
4484
+ if (this.keyIs(stream.key(pair), step)) {
4485
+ return pair
4486
+ }
4487
+ i -= 1
4488
+ }
4489
+ return -1
4490
+ }
4491
+
4492
+ /**
4493
+ * Internal: the pair `step` matches in the maps the merge keys of map `map`
4494
+ * name, in the order they are written, each searched as `ownPair` and then
4495
+ * through its own merge keys, so the first map listed wins. A merge key's
4496
+ * value is a map, an alias to one or a sequence of those; anything else
4497
+ * merges nothing. A map already searched is skipped (two merges sharing a
4498
+ * map search it once), and the search goes `YAML_ALIAS_HOPS` merges deep at
4499
+ * most. No YAML source can form a merge cycle, since an anchor binds only
4500
+ * once its node ends, so both guards are defensive: they bound the work, not
4501
+ * a loop.
4502
+ */
4503
+ mergedPair(map: i32, step: string, depth: i32): i32 {
4504
+ const stream: YamlStream = this.stream
4505
+ const count: i32 = stream.count(map)
4506
+ let i: i32 = 0
4507
+ while (i < count && depth < YAML_ALIAS_HOPS) {
4508
+ const pair: i32 = stream.child(map, i)
4509
+ if (yamlIsMergeKey(stream, stream.key(pair))) {
4510
+ const value: i32 = this.follow(stream.value(pair))
4511
+ if (value >= 0 && stream.kind(value) === YAML_SEQ) {
4512
+ const items: i32 = stream.count(value)
4513
+ let j: i32 = 0
4514
+ while (j < items) {
4515
+ const found: i32 = this.mergedFrom(this.follow(stream.child(value, j)), step, depth)
4516
+ if (found >= 0) {
4517
+ return found
4518
+ }
4519
+ j += 1
4520
+ }
4521
+ } else {
4522
+ const found: i32 = this.mergedFrom(value, step, depth)
4523
+ if (found >= 0) {
4524
+ return found
4525
+ }
4526
+ }
4527
+ }
4528
+ i += 1
4529
+ }
4530
+ return -1
4531
+ }
4532
+
4533
+ /** Internal: `mergedPair`'s search of one merged node `from`, which merges nothing unless it is a map not yet searched. */
4534
+ mergedFrom(from: i32, step: string, depth: i32): i32 {
4535
+ const stream: YamlStream = this.stream
4536
+ if (from < 0 || stream.kind(from) !== YAML_MAP) {
4537
+ return -1
4538
+ }
4539
+ if (!this.markSeen(from)) {
4540
+ return -1
4541
+ }
4542
+ const own: i32 = this.ownPair(from, step)
4543
+ return own >= 0 ? own : this.mergedPair(from, step, depth + 1)
4544
+ }
4545
+
4546
+ /**
4547
+ * Internal: starts a step's merge search: a new `mergeStep` stamp, with
4548
+ * `mergeStamps` grown to every node. The stamps are cleared only when the
4549
+ * count wraps, so a step costs what it searches, not the document's size.
4550
+ */
4551
+ startMerge(): void {
4552
+ const stream: YamlStream = this.stream
4553
+ const stamps: i32[] = stream.mergeStamps
4554
+ while (toI32(stamps.length) < stream.top) {
4555
+ stamps.push(0)
4556
+ }
4557
+ if (stream.mergeStep === 2147483647) {
4558
+ let k: i32 = 0
4559
+ while (k < toI32(stamps.length)) {
4560
+ stamps[k] = 0
4561
+ k += 1
4562
+ }
4563
+ stream.mergeStep = 0
4564
+ }
4565
+ stream.mergeStep += 1
4566
+ }
4567
+
4568
+ /** Internal: marks map `map` searched by this step; `false` when it already was. */
4569
+ markSeen(map: i32): boolean {
4570
+ const stream: YamlStream = this.stream
4571
+ const stamps: i32[] = stream.mergeStamps
4572
+ if (map < 0 || map >= toI32(stamps.length) || stamps[map] === stream.mergeStep) {
4573
+ return false
4574
+ }
4575
+ stamps[map] = stream.mergeStep
4576
+ return true
4577
+ }
4578
+
4579
+ /** Internal: walks `path` down from `root`, as far as it matches. */
4580
+ walk(root: i32, path: string[]): void {
4581
+ const stream: YamlStream = this.stream
4582
+ this.node = root
4583
+ this.pair = -1
4584
+ this.matched = 0
4585
+ const steps: i32 = toI32(path.length)
4586
+ while (this.matched < steps) {
4587
+ const at: i32 = this.follow(this.node)
4588
+ if (at < 0) {
4589
+ return
4590
+ }
4591
+ const step: string = path[this.matched]
4592
+ const kind: i32 = stream.kind(at)
4593
+ if (kind === YAML_MAP) {
4594
+ let pair: i32 = this.ownPair(at, step)
4595
+ if (pair < 0 && stream.merge) {
4596
+ this.startMerge()
4597
+ this.markSeen(at)
4598
+ pair = this.mergedPair(at, step, toI32(0))
4599
+ }
4600
+ if (pair < 0) {
4601
+ return
4602
+ }
4603
+ // An empty value (`a:`) matches the step and reaches no node, which ends the walk.
4604
+ this.pair = pair
4605
+ this.node = stream.value(pair)
4606
+ this.matched += 1
4607
+ if (this.node < 0) {
4608
+ return
4609
+ }
4610
+ } else if (kind === YAML_SEQ) {
4611
+ const index: i32 = this.indexOf(step)
4612
+ if (index < 0 || index >= stream.count(at)) {
4613
+ return
4614
+ }
4615
+ this.node = stream.child(at, index)
4616
+ this.matched += 1
4617
+ } else {
4618
+ return
4619
+ }
4620
+ }
4621
+ }
4622
+ }
4623
+
4624
+ /**
4625
+ * The node `path` names below `root` of `stream`: each step a map key (matched
4626
+ * by `keyText`; of duplicate keys, the last) or a sequence index (`"0"`, no
4627
+ * leading zero), through aliases. While the stream merges (`YamlOptions.merge`),
4628
+ * a key a map does not hold is looked for in the maps its `<<` keys name, in
4629
+ * order: the map's own keys win, then the first merged map that has it, and the
4630
+ * pair found is the one inside that map. When a step does not match, the node
4631
+ * the last matching step reached; `root` itself for an empty path. A step onto an empty
4632
+ * value (`a:`) matches and reaches no node, so the answer is then `-1`.
4633
+ */
4634
+ export const pathNode = (stream: YamlStream, root: i32, path: string[]): i32 => {
4635
+ const walker: YamlPathWalker = new YamlPathWalker(stream)
4636
+ walker.walk(root, path)
4637
+ return walker.node
4638
+ }
4639
+
4640
+ /** The last map pair `path` steps through below `root` of `stream` (see `pathNode`), or `-1` for none. */
4641
+ export const pathPair = (stream: YamlStream, root: i32, path: string[]): i32 => {
4642
+ const walker: YamlPathWalker = new YamlPathWalker(stream)
4643
+ walker.walk(root, path)
4644
+ return walker.pair
4645
+ }
4646
+
4647
+ /**
4648
+ * How many steps of `path` match below `root` of `stream` (see `pathNode`):
4649
+ * `path.length` when every step matches, an empty value's included.
4650
+ */
4651
+ export const pathMatch = (stream: YamlStream, root: i32, path: string[]): i32 => {
4652
+ const walker: YamlPathWalker = new YamlPathWalker(stream)
4653
+ walker.walk(root, path)
4654
+ return walker.matched
4655
+ }
4656
+
4657
+ // ---- Parsing --------------------------------------------------------------------
4658
+
4659
+ /** Parses `text` as a YAML stream with `options`. Panics when `maxDepth` is outside 1 to 1000. */
4660
+ export const parseYamlWith = (text: string, options: YamlOptions): YamlStream => {
4661
+ if (options.maxDepth < 1 || options.maxDepth > YAML_MAX_DEPTH) {
4662
+ panic("nish/yaml: maxDepth must be between 1 and 1000")
4663
+ }
4664
+ const parser: YamlParser = new YamlParser(text, options)
4665
+ if (options.firstDocumentOnly) {
4666
+ parser.parseFirst()
4667
+ } else {
4668
+ parser.parseAll()
4669
+ }
4670
+ parser.stream.sortDiagnostics()
4671
+ return parser.stream
4672
+ }
4673
+
4674
+ /** Parses `text` as a YAML stream with the default options. */
4675
+ export const parseYaml = (text: string): YamlStream => parseYamlWith(text, new YamlOptions())