@amritk/nish-x86_64-linux 0.27.2 → 0.29.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.
- package/INSTALL.md +16 -8
- package/bin/nish +0 -0
- package/docs/AI.md +118 -26
- package/package.json +1 -1
- package/runtime/core/host.d.ts +2 -1
- package/runtime/core/host.mjs +4 -0
- package/runtime/core.d.ts +47 -0
- package/runtime/core.mjs +67 -0
- package/runtime/nish.d.ts +176 -0
- package/runtime/nish.h +120 -6
- package/runtime/nish.mjs +24 -4
- package/runtime/runtime-checksum.c +270 -0
- package/runtime/runtime-net.c +386 -5
- package/runtime/runtime-os.c +59 -8
- package/runtime/runtime-parallel.c +81 -5
- package/runtime/runtime-sandbox.c +479 -0
- package/runtime/runtime-sha256.c +239 -0
- package/runtime/runtime-shm.c +541 -0
- package/runtime/runtime.c +827 -791
- package/scripts/build.sh +79 -28
- package/std/README.md +25 -13
- package/std/collections.ts +5 -5
- package/std/compress/checksum.ts +88 -75
- package/std/compress/deflate.ts +224 -0
- package/std/compress/gzip.ts +52 -0
- package/std/crypto/bcrypt.ts +816 -280
- package/std/crypto/hmac.ts +305 -2
- package/std/crypto/jwt.ts +1661 -0
- package/std/json-equal.ts +335 -0
- package/std/json-format.ts +1070 -0
- package/std/json-number.ts +428 -0
- package/std/json-pointer.ts +245 -0
- package/std/json-typed.ts +34 -0
- package/std/json-value.ts +510 -17
- package/std/json.ts +35 -6
- package/std/net/handler.ts +1348 -0
- package/std/net/http-client.ts +97 -16
- package/std/net/http-compress.ts +301 -16
- package/std/net/http1-client.ts +104 -19
- package/std/net/http1-server.ts +127 -24
- package/std/net/http1.ts +24 -4
- package/std/net/outbound.ts +409 -0
- package/std/net/postgres.ts +405 -0
- package/std/net/prefork.ts +344 -20
- package/std/net/websocket.ts +295 -14
- package/std/regex.ts +613 -52
- package/std/secret.ts +1 -1
- package/std/sync/ring.ts +587 -0
- package/std/sync/shm.ts +284 -0
- package/std/term-io.ts +35 -8
- package/std/term-rate.ts +13 -1
- package/std/threads.ts +35 -3
- package/std/yaml-json.ts +841 -0
- /package/runtime/{LICENSE-ryu → LICENSE-zmij} +0 -0
package/std/yaml-json.ts
ADDED
|
@@ -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
|
+
}
|