@amritk/nish-x86_64-linux 0.26.0 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,841 @@
1
+ /**
2
+ * `nish/yaml-json` — one YAML document loaded into a `JsonDocument`, with
3
+ * every value's and key's source range, so that one validator and one
4
+ * `YamlLineIndex` serve a JSON text and a YAML text alike:
5
+ *
6
+ * import { parseYaml } from "nish/yaml"
7
+ * import { JsonDocument, JsonLimits } from "nish/json-value"
8
+ * import { YamlJsonLoader } from "nish/yaml-json"
9
+ *
10
+ * const stream = parseYaml(text)
11
+ * const doc = new JsonDocument(new JsonLimits())
12
+ * const loader = new YamlJsonLoader()
13
+ * const root = loader.load(stream, 0, doc)
14
+ * // root < 0: loader.failed, loader.errorStart, loader.errorEnd
15
+ *
16
+ * `load` clears the document and builds document `d` of the stream into it.
17
+ * Every value's `start` and `end` are the byte range of the YAML node it came
18
+ * from, as `YamlStream.start` and `end` give it (an anchor or a tag in front
19
+ * is not included), and every key's `keyStart` and `keyEnd` the range of its
20
+ * key node, quotes included. A key or value the text leaves out, the value of
21
+ * `a:` or the key of `: b`, is an empty range at the end of its pair.
22
+ *
23
+ * **Scalars** follow the core schema the stream resolves: null, booleans,
24
+ * numbers and strings. A number is kept as JSON text, exactly: a `+` sign is
25
+ * dropped, leading zeros are dropped, `.5` is `0.5` and `1.` is `1.0`, and a
26
+ * `0x` or `0o` integer is written in decimal, however wide (up to
27
+ * `YAML_JSON_MAX_RADIX_DIGITS` digits). `.inf`, `-.inf` and `.nan` have no
28
+ * JSON spelling and are `YAML_JSON_ERR_NOT_FINITE`.
29
+ *
30
+ * **Keys** are `keyText`'s: a string's decoded text, a number's or a
31
+ * boolean's source text, `""` for null, a collection's flow text. Two YAML
32
+ * keys that are different nodes can have the same text (`1` and `"1"`); the
33
+ * second follows the document's `duplicates`, refused by default
34
+ * (`JSON_VALUE_ERR_DUPLICATE`, at the second key) or holding the last value
35
+ * under `JSON_VALUE_DUPLICATE_LAST`, as a parse of that JSON would.
36
+ *
37
+ * **Aliases are expanded** into copies, and **merge keys** (`<<`, when the
38
+ * stream was parsed with `merge`) are applied as `pathNode` reads them: the
39
+ * map's own keys first, in order, then each merged map's keys it does not
40
+ * already have, the first map listed winning, searched through its own merge
41
+ * keys. A copied value keeps the ranges of the nodes it was copied from, so
42
+ * an error inside it points at the anchored text, and the copy's root takes
43
+ * the alias's range, where it is used.
44
+ *
45
+ * **An alias bomb is refused before anything is built.** The values the
46
+ * document expands to are counted first, from the nodes, with each node's
47
+ * count kept, so the count costs one step per node however far the aliases
48
+ * multiply it. Past the document's `maxValues` the load is
49
+ * `YAML_JSON_ERR_EXPANSION`, at the first alias whose copy is the largest
50
+ * (the first whose copy alone is past the limit, in a bomb), or
51
+ * `JSON_VALUE_ERR_VALUES` when the document has no alias. A merged map counts
52
+ * all its keys, also those the merging map overrides, so a document within a
53
+ * few values of the limit can be refused for merges that would have fit.
54
+ * The loader's merging is linear too: a map is merged into a given map at
55
+ * most once, however many paths through the merge keys reach it, and the
56
+ * pairs every merge visits and the items of every merge sequence it walks are
57
+ * charged to a budget of `maxValues` plus the stream's node count, past which
58
+ * the load is `YAML_JSON_ERR_EXPANSION` at the merge key. `parseYaml`'s own
59
+ * merge check is not bounded this way: it walks a shared merge sequence once
60
+ * for each merge key that names it.
61
+ *
62
+ * **A merge key's value** is a mapping, an alias to one, or a sequence of
63
+ * those; anything else is refused at the value, by the parser as
64
+ * `YAML_ERR_BAD_MERGE` (so `YAML_JSON_ERR_YAML`) and by the loader as
65
+ * `YAML_JSON_ERR_MERGE`.
66
+ * The text the copies store is bounded too, while they are built: past the
67
+ * document's `JsonLimits.maxTotalBytes` of string, key and number text, the
68
+ * load is `YAML_JSON_ERR_EXPANSION` at the outermost alias being copied, or
69
+ * `JSON_TOK_ERR_TOTAL_LIMIT` at the node when no alias is.
70
+ *
71
+ * **Nesting** is held to `maxDepth`, `JSON_WRITER_MAX_DEPTH` (64) by default,
72
+ * counting copies, so that every loaded document can be written back as JSON;
73
+ * deeper is `YAML_JSON_ERR_DEPTH`, at the node that is one too deep.
74
+ *
75
+ * A document with an error diagnostic is `YAML_JSON_ERR_YAML`, at the first
76
+ * one's range, and loads nothing: a node the parser gave up on has no value
77
+ * to load. Warnings are not errors.
78
+ *
79
+ * A failed load clears the document and answers the code, which is also in
80
+ * `failed`, with the range it is about in `errorStart` and `errorEnd`.
81
+ * `yamlJsonErrorName` names a code. A map of `n` keys costs `O(n)` steps: the
82
+ * loader finds repeated keys itself, with a hash map past
83
+ * `YAML_JSON_SCAN_KEYS` keys, and adds each member through `JsonDocument`'s
84
+ * internal `append`, where `set` would scan the members for every key it
85
+ * adds. Every member it adds is a value it has just made, which is the
86
+ * check `append` skips.
87
+ *
88
+ * Every name here is `yamlJson`-prefixed, because a `std/` module is compiled
89
+ * into the program that imports it and the symbol namespace is flat
90
+ * (`std/README.md`). Every offset, count and code is an `i32`, and the module
91
+ * runs under the Node prelude, where offsets count UTF-16 code units, as the
92
+ * stream's do.
93
+ */
94
+
95
+ import { JSON_WRITER_MAX_DEPTH } from "nish/json"
96
+ import { JSON_TOK_ERR_TOTAL_LIMIT, JsonLimits } from "nish/json-tokenizer"
97
+ import {
98
+ JSON_VALUE_DUPLICATE_LAST,
99
+ JSON_VALUE_ERR_DUPLICATE,
100
+ JSON_VALUE_ERR_VALUES,
101
+ JSON_VALUE_MAX_VALUES,
102
+ JsonDocument,
103
+ JsonValue,
104
+ } from "nish/json-value"
105
+ import {
106
+ YAML_ALIAS,
107
+ YAML_BOOL,
108
+ YAML_FLOAT,
109
+ YAML_INT,
110
+ YAML_MAP,
111
+ YAML_NULL,
112
+ YAML_PLAIN,
113
+ YAML_SCALAR,
114
+ YAML_SEQ,
115
+ YamlStream,
116
+ keyText,
117
+ parseYaml,
118
+ } from "nish/yaml"
119
+
120
+ /** Document `d` is not in the stream. */
121
+ export const YAML_JSON_ERR_DOCUMENT: i32 = -40
122
+ /** The document has an error diagnostic; the range is the first one's. */
123
+ export const YAML_JSON_ERR_YAML: i32 = -41
124
+ /** `.inf`, `-.inf` or `.nan`, which JSON cannot spell. */
125
+ export const YAML_JSON_ERR_NOT_FINITE: i32 = -42
126
+ /** The aliases and merge keys expand the document past `maxValues`; the range is the first largest alias's. */
127
+ export const YAML_JSON_ERR_EXPANSION: i32 = -43
128
+ /** Collections nest deeper than `maxDepth`, counting copies. */
129
+ export const YAML_JSON_ERR_DEPTH: i32 = -44
130
+ /** A `0x` or `0o` integer with more than `YAML_JSON_MAX_RADIX_DIGITS` digits. */
131
+ export const YAML_JSON_ERR_NUMBER: i32 = -45
132
+ /**
133
+ * A merge key's value is not a mapping or a sequence of mappings; the range is
134
+ * the value's. `parseYaml` refuses these itself (`YAML_ERR_BAD_MERGE`, a load's
135
+ * `YAML_JSON_ERR_YAML`); this is the loader's own check of the stream it is given.
136
+ */
137
+ export const YAML_JSON_ERR_MERGE: i32 = -46
138
+
139
+ /**
140
+ * The most digits a `0x` or `0o` integer may have, which is
141
+ * `JSON_DEFAULT_MAX_NUMBER_BYTES`: writing one in decimal costs its digit count
142
+ * times its decimal length, so a limit keeps a long one from costing the square
143
+ * of the text.
144
+ */
145
+ export const YAML_JSON_MAX_RADIX_DIGITS: i32 = 256
146
+
147
+ /** Up to this many keys a map's keys are compared with each other; past it, through a hash map. */
148
+ export const YAML_JSON_SCAN_KEYS: i32 = 16
149
+
150
+ /** How many merge keys deep a merged map is searched, as `YAML_ALIAS_HOPS` bounds `pathNode`. */
151
+ const YAML_JSON_MERGE_HOPS: i32 = 100
152
+
153
+ /** The decimal limb `yamlJsonRadix` works in: four digits, so a limb times 16 plus a carry fits an `i32`. */
154
+ const YAML_JSON_LIMB: i32 = 10000
155
+
156
+ const YAML_JSON_PLUS: i32 = 43
157
+ const YAML_JSON_DASH: i32 = 45
158
+ const YAML_JSON_DOT: i32 = 46
159
+ const YAML_JSON_ZERO: i32 = 48
160
+ const YAML_JSON_LT: i32 = 60
161
+
162
+ /** A `YAML_JSON_ERR_*` code's name, without the prefix; `""` for any other code. */
163
+ export const yamlJsonErrorName = (code: i32): string => {
164
+ const names: string[] = ["DOCUMENT", "YAML", "NOT_FINITE", "EXPANSION", "DEPTH", "NUMBER", "MERGE"]
165
+ const i: i32 = YAML_JSON_ERR_DOCUMENT - code
166
+ return i >= 0 && i < toI32(names.length) ? names[i] : ""
167
+ }
168
+
169
+ /**
170
+ * Loads documents of a `YamlStream` into a `JsonDocument`. Keeps its scratch
171
+ * from one load to the next. Its fields and its "Internal" methods are
172
+ * reachable, as every member in Nish is; use `load` and the result fields.
173
+ */
174
+ export class YamlJsonLoader {
175
+ /** How deeply collections may nest, counting copies; `JSON_WRITER_MAX_DEPTH` so the result can be written. */
176
+ maxDepth: i32 = 0
177
+ /** The last `load`'s code: `0`, a `YAML_JSON_ERR_*` code or a `JSON_VALUE_ERR_*` code. */
178
+ failed: i32 = 0
179
+ /** The byte range the error is about, and `-1` until one. */
180
+ errorStart: i32 = -1
181
+ errorEnd: i32 = -1
182
+
183
+ /** Internal: the stream and document of the load in progress. */
184
+ stream: YamlStream
185
+ doc: JsonDocument
186
+ /** Internal: per node, the values it expands to (`0` not counted yet, `-1` being counted). */
187
+ sizes: i32[]
188
+ /** Internal: the nodes the last count set a size on, the first `countedTop` of them: all a count clears. */
189
+ counted: i32[]
190
+ countedTop: i32 = 0
191
+ /** Internal: the count's own stack of nodes. */
192
+ pending: i32[]
193
+ /** Internal: the most values the document may hold; a size saturates one past it. */
194
+ cap: i32 = 0
195
+ /** Internal: the alias with the largest copy, and its size. */
196
+ bigAlias: i32 = -1
197
+ bigSize: i32 = 0
198
+ /** Internal: the outermost alias being copied (`-1` for none), and the text the document may store. */
199
+ copying: i32 = -1
200
+ byteCap: i32 = 0
201
+ /** Internal: where the next map member goes in the `entry` arrays. */
202
+ entryTop: i32 = 0
203
+ /**
204
+ * Internal: the members of the maps being built, one map's after another's:
205
+ * each one's key text and pair, and the value node it holds (`-1` for none).
206
+ */
207
+ entryKeys: string[]
208
+ entryPairs: i32[]
209
+ entryValues: i32[]
210
+ /** Internal: a large map's key texts, to their entry. */
211
+ seen: Map<string, i32>
212
+ /** Internal: per node, the stamp of the last map it was merged into; and that map's stamp. */
213
+ mergeMarks: i32[]
214
+ mergeStamp: i32 = 0
215
+ /** Internal: the merged pairs the load may still visit. */
216
+ mergeBudget: i32 = 0
217
+
218
+ constructor() {
219
+ this.maxDepth = JSON_WRITER_MAX_DEPTH
220
+ this.stream = parseYaml("")
221
+ this.doc = new JsonDocument(new JsonLimits())
222
+ this.sizes = []
223
+ this.counted = []
224
+ this.pending = []
225
+ this.entryKeys = []
226
+ this.entryPairs = []
227
+ this.entryValues = []
228
+ this.seen = new Map<string, i32>()
229
+ this.mergeMarks = []
230
+ }
231
+
232
+ /**
233
+ * Clears `doc` and loads document `d` of `stream` into it. Answers the root
234
+ * value, or a negative code, which is also in `failed`, with the range in
235
+ * `errorStart` and `errorEnd`; nothing is kept from a load that fails.
236
+ */
237
+ load(stream: YamlStream, d: i32, doc: JsonDocument): JsonValue {
238
+ this.stream = stream
239
+ this.doc = doc
240
+ this.failed = 0
241
+ this.errorStart = -1
242
+ this.errorEnd = -1
243
+ this.entryTop = 0
244
+ this.copying = -1
245
+ this.byteCap = doc.tok.maxTotalBytes
246
+ doc.clear()
247
+ const origin: i32 = 0
248
+ if (d < 0 || d >= stream.documentCount()) {
249
+ return this.fail(YAML_JSON_ERR_DOCUMENT, origin, origin)
250
+ }
251
+ for (let i: i32 = 0; i < stream.diagnosticCount(); i += 1) {
252
+ if (stream.diagnosticDocument(i) === d && !stream.diagnosticIsWarning(i)) {
253
+ return this.fail(YAML_JSON_ERR_YAML, stream.diagnosticStart(i), stream.diagnosticEnd(i))
254
+ }
255
+ }
256
+ const root: i32 = stream.documentRoot(d)
257
+ if (root < 0) {
258
+ const at: i32 = stream.documentStart(d) < 0 ? 0 : stream.documentStart(d)
259
+ return this.nullAt(at)
260
+ }
261
+ const room: i32 = doc.maxValues < JSON_VALUE_MAX_VALUES ? doc.maxValues : JSON_VALUE_MAX_VALUES
262
+ if (this.count(root, room) > room) {
263
+ if (this.bigAlias >= 0) {
264
+ return this.fail(YAML_JSON_ERR_EXPANSION, stream.start(this.bigAlias), stream.end(this.bigAlias))
265
+ }
266
+ return this.fail(JSON_VALUE_ERR_VALUES, stream.start(root), stream.end(root))
267
+ }
268
+ this.mergeBudget = room + toI32(stream.kinds.length)
269
+ return this.build(root, origin)
270
+ }
271
+
272
+ /** Internal: records a failed load, clearing the document, and answers `code`. */
273
+ fail(code: i32, start: i32, end: i32): i32 {
274
+ this.failed = code
275
+ this.errorStart = start
276
+ this.errorEnd = end
277
+ this.entryTop = 0
278
+ this.doc.clear()
279
+ return code
280
+ }
281
+
282
+ /** Internal: `v`, given the range `[start, end)`, or the failure the document answers. */
283
+ ranged(v: JsonValue, start: i32, end: i32): JsonValue {
284
+ if (v < 0) {
285
+ return this.fail(v, start, end)
286
+ }
287
+ const code: i32 = this.doc.setRange(v, start, end)
288
+ return code < 0 ? this.fail(code, start, end) : v
289
+ }
290
+
291
+ /** Internal: a null with the empty range at `at`. */
292
+ nullAt(at: i32): JsonValue {
293
+ return this.ranged(this.doc.newNull(), at, at)
294
+ }
295
+
296
+ // ---- Counting ------------------------------------------------------------------
297
+
298
+ /**
299
+ * Internal: how many values node `root` expands to, saturating at `cap + 1`.
300
+ * Each node is counted once and its count kept, on a stack of its own
301
+ * rather than by recursion, because a chain of aliases can be as long as
302
+ * the text. Sets `bigAlias`. Clears only the sizes the last count set, so a
303
+ * stream of many documents is not cleared whole for each.
304
+ */
305
+ count(root: i32, cap: i32): i32 {
306
+ const stream: YamlStream = this.stream
307
+ const nodes: i32 = toI32(stream.kinds.length)
308
+ const sizes: i32[] = this.sizes
309
+ while (toI32(sizes.length) < nodes) {
310
+ sizes.push(0)
311
+ }
312
+ const counted: i32[] = this.counted
313
+ for (let i: i32 = 0; i < this.countedTop && i < toI32(counted.length); i += 1) {
314
+ const c: i32 = counted[i]
315
+ if (c >= 0 && c < toI32(sizes.length)) {
316
+ sizes[c] = 0
317
+ }
318
+ }
319
+ this.countedTop = 0
320
+ this.cap = cap
321
+ this.bigAlias = -1
322
+ this.bigSize = 0
323
+ const pending: i32[] = this.pending
324
+ let top: i32 = 0
325
+ top = this.pushNode(top, root)
326
+ while (top > 0) {
327
+ const n: i32 = pending[top - 1]
328
+ // A pushed node is in range; the test proves it to the bounds check.
329
+ if (n < 0 || n >= toI32(sizes.length) || sizes[n] > 0) {
330
+ top -= 1
331
+ } else if (sizes[n] === 0) {
332
+ // First visit: count the nodes it is made of first.
333
+ sizes[n] = -1
334
+ if (this.countedTop < toI32(counted.length)) {
335
+ counted[this.countedTop] = n
336
+ } else {
337
+ counted.push(n)
338
+ }
339
+ this.countedTop += 1
340
+ top = this.pushParts(top, n)
341
+ } else {
342
+ const size: i32 = this.sizeOf(n)
343
+ if (n < toI32(sizes.length)) {
344
+ sizes[n] = size
345
+ }
346
+ top -= 1
347
+ }
348
+ }
349
+ return sizes[root]
350
+ }
351
+
352
+ /** Internal: pushes node `n` onto the count's stack unless it is counted; answers the new top. */
353
+ pushNode(top: i32, n: i32): i32 {
354
+ if (n < 0 || this.sizes[n] !== 0) {
355
+ return top
356
+ }
357
+ if (top < toI32(this.pending.length)) {
358
+ this.pending[top] = n
359
+ } else {
360
+ this.pending.push(n)
361
+ }
362
+ return top + 1
363
+ }
364
+
365
+ /** Internal: pushes the nodes node `n`'s count is the sum of. */
366
+ pushParts(top: i32, n: i32): i32 {
367
+ const stream: YamlStream = this.stream
368
+ const kind: i32 = stream.kind(n)
369
+ let t: i32 = top
370
+ if (kind === YAML_ALIAS) {
371
+ return this.pushNode(t, stream.resolved(n))
372
+ }
373
+ if (kind === YAML_SEQ) {
374
+ for (let i: i32 = 0; i < stream.count(n); i += 1) {
375
+ t = this.pushNode(t, stream.child(n, i))
376
+ }
377
+ } else if (kind === YAML_MAP) {
378
+ for (let i: i32 = 0; i < stream.count(n); i += 1) {
379
+ t = this.pushNode(t, stream.value(stream.child(n, i)))
380
+ }
381
+ }
382
+ return t
383
+ }
384
+
385
+ /**
386
+ * Internal: node `n`'s count from its parts' counts, all of them kept by
387
+ * now; a part still being counted is a cycle, which counts as too many.
388
+ */
389
+ sizeOf(n: i32): i32 {
390
+ const stream: YamlStream = this.stream
391
+ const kind: i32 = stream.kind(n)
392
+ if (kind === YAML_ALIAS) {
393
+ const size: i32 = this.partSize(stream.resolved(n))
394
+ if (size > this.bigSize) {
395
+ this.bigAlias = n
396
+ this.bigSize = size
397
+ }
398
+ return size
399
+ }
400
+ let total: i32 = 1
401
+ if (kind === YAML_SEQ) {
402
+ for (let i: i32 = 0; i < stream.count(n); i += 1) {
403
+ total = this.plus(total, this.partSize(stream.child(n, i)))
404
+ }
405
+ } else if (kind === YAML_MAP) {
406
+ for (let i: i32 = 0; i < stream.count(n); i += 1) {
407
+ const pair: i32 = stream.child(n, i)
408
+ const value: i32 = stream.value(pair)
409
+ // A merged map's members join this one's; its own value is not one.
410
+ const merged: boolean = this.isMergeKey(stream.key(pair))
411
+ const part: i32 = this.partSize(value)
412
+ let less: i32 = merged ? 1 : 0
413
+ if (merged && stream.kind(stream.resolved(value)) === YAML_SEQ) {
414
+ // A list of maps: the list itself is not one either, nor any map in it.
415
+ less += stream.count(stream.resolved(value))
416
+ }
417
+ // A saturated part stays saturated: taking the adjustment off it
418
+ // would bring a bomb back under the cap.
419
+ total = part > this.cap ? this.cap + 1 : this.plus(total, part - less)
420
+ }
421
+ }
422
+ return total
423
+ }
424
+
425
+ /** Internal: a counted part's size: `1` for a missing node, too many for one still being counted. */
426
+ partSize(n: i32): i32 {
427
+ if (n < 0) {
428
+ return 1
429
+ }
430
+ const size: i32 = this.sizes[n]
431
+ return size < 0 ? this.cap + 1 : size
432
+ }
433
+
434
+ /** Internal: `a + b`, saturating at `cap + 1`; both are at most that, so the sum fits. */
435
+ plus(a: i32, b: i32): i32 {
436
+ const sum: i32 = a + b
437
+ return sum > this.cap ? this.cap + 1 : sum
438
+ }
439
+
440
+ // ---- Building ------------------------------------------------------------------
441
+
442
+ /** Internal: builds node `n`, nested `depth` collections deep, with `n`'s range on the value it answers. */
443
+ build(n: i32, depth: i32): JsonValue {
444
+ const stream: YamlStream = this.stream
445
+ const at: i32 = stream.resolved(n)
446
+ const kind: i32 = stream.kind(at)
447
+ let v: JsonValue = 0
448
+ const outer: boolean = this.copying < 0 && stream.kind(n) === YAML_ALIAS
449
+ if (outer) {
450
+ this.copying = n
451
+ }
452
+ if (kind === YAML_MAP || kind === YAML_SEQ) {
453
+ if (depth >= this.maxDepth) {
454
+ return this.fail(YAML_JSON_ERR_DEPTH, stream.start(n), stream.end(n))
455
+ }
456
+ v = kind === YAML_MAP ? this.buildMap(at, depth + 1) : this.buildSeq(at, depth + 1)
457
+ } else {
458
+ v = this.buildScalar(at)
459
+ if (v >= 0 && this.doc.byteTop > this.byteCap) {
460
+ return this.tooMuchText(n)
461
+ }
462
+ }
463
+ if (outer) {
464
+ this.copying = -1
465
+ }
466
+ if (v < 0) {
467
+ // The failure is recorded already, with its own range.
468
+ return this.failed < 0 ? v : this.fail(v, stream.start(n), stream.end(n))
469
+ }
470
+ return this.ranged(v, stream.start(n), stream.end(n))
471
+ }
472
+
473
+ /** Internal: the failure for text past `byteCap`, stored for node `n`. */
474
+ tooMuchText(n: i32): i32 {
475
+ const stream: YamlStream = this.stream
476
+ const a: i32 = this.copying
477
+ if (a >= 0) {
478
+ return this.fail(YAML_JSON_ERR_EXPANSION, stream.start(a), stream.end(a))
479
+ }
480
+ return this.fail(JSON_TOK_ERR_TOTAL_LIMIT, stream.start(n), stream.end(n))
481
+ }
482
+
483
+ /** Internal: a scalar's value, by its core-schema type. */
484
+ buildScalar(at: i32): JsonValue {
485
+ const stream: YamlStream = this.stream
486
+ const doc: JsonDocument = this.doc
487
+ const type: i32 = stream.type(at)
488
+ if (type === YAML_NULL) {
489
+ return doc.newNull()
490
+ }
491
+ if (type === YAML_BOOL) {
492
+ return doc.newBoolean(stream.boolValue(at))
493
+ }
494
+ if (type === YAML_INT || type === YAML_FLOAT) {
495
+ const text: string = yamlJsonNumber(stream.text(at))
496
+ if (text === "") {
497
+ return this.fail(YAML_JSON_ERR_NOT_FINITE, stream.start(at), stream.end(at))
498
+ }
499
+ if (text === "#") {
500
+ return this.fail(YAML_JSON_ERR_NUMBER, stream.start(at), stream.end(at))
501
+ }
502
+ return doc.newNumber(text)
503
+ }
504
+ return doc.newString(stream.text(at))
505
+ }
506
+
507
+ /** Internal: a sequence's array, each entry built in turn. */
508
+ buildSeq(at: i32, depth: i32): JsonValue {
509
+ const stream: YamlStream = this.stream
510
+ const doc: JsonDocument = this.doc
511
+ const a: JsonValue = doc.newArray()
512
+ if (a < 0) {
513
+ return this.fail(a, stream.start(at), stream.end(at))
514
+ }
515
+ for (let i: i32 = 0; i < stream.count(at); i += 1) {
516
+ const c: i32 = stream.child(at, i)
517
+ const v: JsonValue = c < 0 ? this.nullAt(stream.end(at)) : this.build(c, depth)
518
+ if (v < 0) {
519
+ return v
520
+ }
521
+ const code: i32 = doc.push(a, v)
522
+ if (code < 0) {
523
+ return this.fail(code, stream.start(at), stream.end(at))
524
+ }
525
+ }
526
+ return a
527
+ }
528
+
529
+ /**
530
+ * Internal: a map's object. Its members are gathered first, own keys then
531
+ * merged ones, with repeats settled; then each value is built and added.
532
+ */
533
+ buildMap(at: i32, depth: i32): JsonValue {
534
+ const stream: YamlStream = this.stream
535
+ const doc: JsonDocument = this.doc
536
+ const base: i32 = this.entryTop
537
+ // Clearing costs the table's capacity, which a large map leaves behind.
538
+ if (this.seen.size > 0) {
539
+ this.seen.clear()
540
+ }
541
+ for (let i: i32 = 0; i < stream.count(at); i += 1) {
542
+ const pair: i32 = stream.child(at, i)
543
+ if (!this.isMergeKey(stream.key(pair)) && this.addEntry(base, pair, true) < 0) {
544
+ return this.failed
545
+ }
546
+ }
547
+ const outer: i32 = 0
548
+ this.newMergeStamp()
549
+ this.markMerged(at)
550
+ if (this.mergeInto(base, at, outer) < 0) {
551
+ return this.failed
552
+ }
553
+ const top: i32 = this.entryTop
554
+ const o: JsonValue = doc.newObject()
555
+ if (o < 0) {
556
+ return this.fail(o, stream.start(at), stream.end(at))
557
+ }
558
+ const ox: i32 = doc.index(o)
559
+ for (let e: i32 = base; e < top; e += 1) {
560
+ const pair: i32 = this.entryPairs[e]
561
+ const valueNode: i32 = this.entryValues[e]
562
+ const v: JsonValue = valueNode < 0 ? this.nullAt(stream.end(pair)) : this.build(valueNode, depth)
563
+ if (v < 0) {
564
+ return v
565
+ }
566
+ const key: string = this.entryKeys[e]
567
+ const keyAt: i32 = doc.byteTop
568
+ doc.append(ox, doc.index(v), keyAt, doc.storeText(key))
569
+ const keyNode: i32 = stream.key(pair)
570
+ if (doc.byteTop > this.byteCap) {
571
+ return this.tooMuchText(keyNode < 0 ? pair : keyNode)
572
+ }
573
+ const keyStart: i32 = keyNode < 0 ? stream.start(pair) : stream.start(keyNode)
574
+ const keyEnd: i32 = keyNode < 0 ? keyStart : stream.end(keyNode)
575
+ const code: i32 = doc.setKeyRange(o, e - base, keyStart, keyEnd)
576
+ if (code < 0) {
577
+ return this.fail(code, keyStart, keyEnd)
578
+ }
579
+ }
580
+ this.entryTop = base
581
+ return o
582
+ }
583
+
584
+ /**
585
+ * Internal: adds the merge keys' maps of map `at` to the entries from
586
+ * `base`, each key only if it is not there yet: every map a merge key
587
+ * names, in order, its own keys first and then its merge keys', `hops` deep.
588
+ */
589
+ mergeInto(base: i32, at: i32, hops: i32): i32 {
590
+ const stream: YamlStream = this.stream
591
+ for (let i: i32 = 0; i < stream.count(at); i += 1) {
592
+ const pair: i32 = stream.child(at, i)
593
+ if (this.isMergeKey(stream.key(pair))) {
594
+ if (hops >= YAML_JSON_MERGE_HOPS) {
595
+ return this.fail(YAML_JSON_ERR_DEPTH, stream.start(pair), stream.end(pair))
596
+ }
597
+ const value: i32 = stream.value(pair)
598
+ const source: i32 = stream.resolved(value)
599
+ if (source >= 0 && stream.kind(source) === YAML_MAP) {
600
+ if (this.mergeMap(base, pair, source, hops + 1) < 0) {
601
+ return this.failed
602
+ }
603
+ } else if (source >= 0 && stream.kind(source) === YAML_SEQ) {
604
+ // The walk itself is charged, since its items may all be merged already.
605
+ this.mergeBudget -= stream.count(source)
606
+ if (this.mergeBudget < 0) {
607
+ return this.fail(YAML_JSON_ERR_EXPANSION, stream.start(pair), stream.end(pair))
608
+ }
609
+ for (let j: i32 = 0; j < stream.count(source); j += 1) {
610
+ const item: i32 = stream.child(source, j)
611
+ const m: i32 = stream.resolved(item)
612
+ if (m < 0 || stream.kind(m) !== YAML_MAP) {
613
+ const bad: i32 = item < 0 ? value : item
614
+ return this.fail(YAML_JSON_ERR_MERGE, stream.start(bad), stream.end(bad))
615
+ }
616
+ if (this.mergeMap(base, pair, m, hops + 1) < 0) {
617
+ return this.failed
618
+ }
619
+ }
620
+ } else {
621
+ const bad: i32 = value < 0 ? pair : value
622
+ return this.fail(YAML_JSON_ERR_MERGE, stream.start(bad), stream.end(bad))
623
+ }
624
+ }
625
+ }
626
+ return 0
627
+ }
628
+
629
+ /**
630
+ * Internal: merges map `m`'s own keys, then its merge keys' maps, for the
631
+ * merge key `pair`; nothing if `m` is merged into this map already, since
632
+ * a second merge would find every key taken.
633
+ */
634
+ mergeMap(base: i32, pair: i32, m: i32, hops: i32): i32 {
635
+ const stream: YamlStream = this.stream
636
+ if (!this.markMerged(m)) {
637
+ return 0
638
+ }
639
+ this.mergeBudget -= stream.count(m) + 1
640
+ if (this.mergeBudget < 0) {
641
+ return this.fail(YAML_JSON_ERR_EXPANSION, stream.start(pair), stream.end(pair))
642
+ }
643
+ for (let i: i32 = 0; i < stream.count(m); i += 1) {
644
+ const own: i32 = stream.child(m, i)
645
+ if (!this.isMergeKey(stream.key(own)) && this.addEntry(base, own, false) < 0) {
646
+ return this.failed
647
+ }
648
+ }
649
+ return this.mergeInto(base, m, hops)
650
+ }
651
+
652
+ /** Internal: starts a map's merges, so no node is marked as merged into it yet. */
653
+ newMergeStamp(): void {
654
+ const nodes: i32 = toI32(this.stream.kinds.length)
655
+ const marks: i32[] = this.mergeMarks
656
+ while (toI32(marks.length) < nodes) {
657
+ marks.push(0)
658
+ }
659
+ this.mergeStamp += 1
660
+ if (this.mergeStamp >= JSON_VALUE_MAX_VALUES) {
661
+ // Far past any one load: start the stamps over rather than let them wrap.
662
+ for (let i: i32 = 0; i < toI32(marks.length); i += 1) {
663
+ marks[i] = 0
664
+ }
665
+ this.mergeStamp = 1
666
+ }
667
+ }
668
+
669
+ /** Internal: marks node `n` as merged into the current map; `false` if it already was. */
670
+ markMerged(n: i32): boolean {
671
+ const marks: i32[] = this.mergeMarks
672
+ if (n < 0 || n >= toI32(marks.length) || marks[n] === this.mergeStamp) {
673
+ return false
674
+ }
675
+ marks[n] = this.mergeStamp
676
+ return true
677
+ }
678
+
679
+ /**
680
+ * Internal: adds pair `pair` to the entries of the map from `base`. A key
681
+ * already there is skipped for a merged pair; for an own pair it follows
682
+ * the document's `duplicates`, keeping the first key and its place.
683
+ */
684
+ addEntry(base: i32, pair: i32, own: boolean): i32 {
685
+ const stream: YamlStream = this.stream
686
+ const keyNode: i32 = stream.key(pair)
687
+ const key: string = keyNode < 0 ? "" : keyText(stream, keyNode)
688
+ const found: i32 = this.findEntry(base, key)
689
+ if (found >= 0) {
690
+ if (!own) {
691
+ return 0
692
+ }
693
+ if (this.doc.duplicates !== JSON_VALUE_DUPLICATE_LAST) {
694
+ const start: i32 = keyNode < 0 ? stream.start(pair) : stream.start(keyNode)
695
+ return this.fail(JSON_VALUE_ERR_DUPLICATE, start, keyNode < 0 ? start : stream.end(keyNode))
696
+ }
697
+ this.entryValues[found] = stream.value(pair)
698
+ return 0
699
+ }
700
+ const e: i32 = this.entryTop
701
+ if (e < toI32(this.entryPairs.length)) {
702
+ this.entryKeys[e] = key
703
+ this.entryPairs[e] = pair
704
+ this.entryValues[e] = stream.value(pair)
705
+ } else {
706
+ this.entryKeys.push(key)
707
+ this.entryPairs.push(pair)
708
+ this.entryValues.push(stream.value(pair))
709
+ }
710
+ this.entryTop = e + 1
711
+ if (e - base >= YAML_JSON_SCAN_KEYS) {
712
+ this.seen.set(key, e)
713
+ }
714
+ return 0
715
+ }
716
+
717
+ /**
718
+ * Internal: the entry from `base` whose key is `key`, or `-1`. The first
719
+ * `YAML_JSON_SCAN_KEYS` are compared one by one; the rest are in `seen`.
720
+ */
721
+ findEntry(base: i32, key: string): i32 {
722
+ const top: i32 = this.entryTop
723
+ const scanned: i32 = top - base < YAML_JSON_SCAN_KEYS ? top : base + YAML_JSON_SCAN_KEYS
724
+ for (let e: i32 = base; e < scanned; e += 1) {
725
+ if (this.entryKeys[e] === key) {
726
+ return e
727
+ }
728
+ }
729
+ if (top - base <= YAML_JSON_SCAN_KEYS) {
730
+ return -1
731
+ }
732
+ const e: i32 | undefined = this.seen.get(key)
733
+ return e ?? -1
734
+ }
735
+
736
+ /**
737
+ * Internal: whether `key` is a merge key, as the stream reads one: a plain
738
+ * `<<`, untagged or `!!merge`. Keep in step with `yamlIsMergeKey` in
739
+ * `std/yaml.ts`, which is not exported.
740
+ */
741
+ isMergeKey(key: i32): boolean {
742
+ const stream: YamlStream = this.stream
743
+ if (!stream.merge || key < 0 || stream.kind(key) !== YAML_SCALAR || stream.style(key) !== YAML_PLAIN) {
744
+ return false
745
+ }
746
+ const start: i32 = stream.start(key)
747
+ if (
748
+ stream.end(key) !== start + 2 ||
749
+ toI32(stream.source.charCodeAt(start)) !== YAML_JSON_LT ||
750
+ toI32(stream.source.charCodeAt(start + 1)) !== YAML_JSON_LT
751
+ ) {
752
+ return false
753
+ }
754
+ const tag: string = stream.tag(key)
755
+ return tag === "" || tag === "merge"
756
+ }
757
+ }
758
+
759
+ // ---- Numbers ---------------------------------------------------------------------
760
+
761
+ /**
762
+ * The JSON spelling of a core-schema number's text: `""` for `.inf`, `-.inf`
763
+ * and `.nan`, `"#"` for a `0x` or `0o` integer longer than
764
+ * `YAML_JSON_MAX_RADIX_DIGITS` digits.
765
+ */
766
+ const yamlJsonNumber = (text: string): string => {
767
+ const n: i32 = toI32(text.length)
768
+ let i: i32 = 0
769
+ let sign: string = ""
770
+ const first: i32 = yamlJsonByte(text, 0)
771
+ if (first === YAML_JSON_PLUS || first === YAML_JSON_DASH) {
772
+ sign = first === YAML_JSON_DASH ? "-" : ""
773
+ i = 1
774
+ }
775
+ if (yamlJsonByte(text, i) === YAML_JSON_DOT && !yamlJsonIsDigit(yamlJsonByte(text, i + 1))) {
776
+ return ""
777
+ }
778
+ const x: i32 = yamlJsonByte(text, i + 1)
779
+ if (yamlJsonByte(text, i) === YAML_JSON_ZERO && (x === 120 || x === 111)) {
780
+ if (n - i - 2 > YAML_JSON_MAX_RADIX_DIGITS) {
781
+ return "#"
782
+ }
783
+ const digits: string = yamlJsonRadix(text, i + 2, x === 120 ? 16 : 8)
784
+ return digits === "0" ? "0" : `${sign}${digits}`
785
+ }
786
+ // Decimal: the whole part without its leading zeros, then the rest as it is.
787
+ let whole: i32 = i
788
+ while (whole < n && yamlJsonIsDigit(yamlJsonByte(text, whole))) {
789
+ whole += 1
790
+ }
791
+ let lead: i32 = i
792
+ while (lead + 1 < whole && yamlJsonByte(text, lead) === YAML_JSON_ZERO) {
793
+ lead += 1
794
+ }
795
+ const wholeDigits: string = whole === i ? "0" : text.substring(lead, whole)
796
+ if (whole === n) {
797
+ return wholeDigits === "0" ? "0" : `${sign}${wholeDigits}`
798
+ }
799
+ let rest: string = text.substring(whole, n)
800
+ if (yamlJsonByte(text, whole) === YAML_JSON_DOT && !yamlJsonIsDigit(yamlJsonByte(text, whole + 1))) {
801
+ // `1.` and `1.e5`: a fraction JSON needs at least one digit of.
802
+ rest = `.0${text.substring(whole + 1, n)}`
803
+ }
804
+ return `${sign}${wholeDigits}${rest}`
805
+ }
806
+
807
+ /** `text[from, end)`, digits in `base` 16 or 8, written in decimal: four digits a limb, the lowest first. */
808
+ const yamlJsonRadix = (text: string, from: i32, base: i32): string => {
809
+ const limbs: i32[] = [0]
810
+ for (let i: i32 = from; i < toI32(text.length); i += 1) {
811
+ let carry: i32 = yamlJsonHexValue(yamlJsonByte(text, i))
812
+ for (let l: i32 = 0; l < toI32(limbs.length); l += 1) {
813
+ const x: i32 = limbs[l] * base + carry
814
+ limbs[l] = x % YAML_JSON_LIMB
815
+ carry = x / YAML_JSON_LIMB
816
+ }
817
+ if (carry > 0) {
818
+ limbs.push(carry)
819
+ }
820
+ }
821
+ const top: i32 = toI32(limbs.length) - 1
822
+ const parts: string[] = [`${limbs[top]}`]
823
+ for (let l: i32 = top - 1; l >= 0 && l < toI32(limbs.length); l -= 1) {
824
+ const limb: string = `${limbs[l]}`
825
+ parts.push(`${"000".substring(0, 4 - toI32(limb.length))}${limb}`)
826
+ }
827
+ return parts.join("")
828
+ }
829
+
830
+ const yamlJsonByte = (text: string, i: i32): i32 =>
831
+ i >= 0 && i < toI32(text.length) ? toI32(text.charCodeAt(i)) : -1
832
+
833
+ const yamlJsonIsDigit = (c: i32): boolean => c >= YAML_JSON_ZERO && c <= 57
834
+
835
+ /** A hex digit's value; the text is the stream's, already checked to be digits of its base. */
836
+ const yamlJsonHexValue = (c: i32): i32 => {
837
+ if (c >= YAML_JSON_ZERO && c <= 57) {
838
+ return c - YAML_JSON_ZERO
839
+ }
840
+ return (c | 32) - 87
841
+ }