@amritk/nish-x86_64-linux 0.18.0 → 0.21.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 +58 -28
- package/bin/nish +0 -0
- package/docs/AI.md +2759 -0
- package/package.json +3 -2
- package/runtime/browser.d.ts +14 -0
- package/runtime/browser.mjs +88 -0
- package/runtime/core/arrays.d.ts +18 -0
- package/runtime/core/arrays.mjs +125 -0
- package/runtime/core/checks.mjs +20 -0
- package/runtime/core/host.d.ts +70 -0
- package/runtime/core/host.mjs +311 -0
- package/runtime/core/numbers.d.ts +53 -0
- package/runtime/core/numbers.mjs +316 -0
- package/runtime/core/result.d.ts +41 -0
- package/runtime/core/result.mjs +127 -0
- package/runtime/core/strings.d.ts +14 -0
- package/runtime/core/strings.mjs +169 -0
- package/runtime/core.d.ts +9 -0
- package/runtime/core.mjs +55 -0
- package/runtime/host.d.ts +6 -0
- package/runtime/nish.d.ts +99 -5
- package/runtime/nish.h +289 -19
- package/runtime/nish.mjs +39 -3
- package/runtime/node.mjs +383 -0
- package/runtime/runtime-aes.c +490 -0
- package/runtime/runtime-bytes.c +401 -0
- package/runtime/runtime-cross.c +366 -0
- package/runtime/runtime-host.c +8 -7
- package/runtime/runtime-net.c +93 -34
- package/runtime/runtime-os.c +214 -16
- package/runtime/runtime-parallel.c +590 -107
- package/runtime/runtime-simd.c +235 -11
- package/runtime/runtime-term.c +504 -0
- package/runtime/runtime-wasm.c +5 -14
- package/runtime/runtime.c +257 -17
- package/runtime/shim.mjs +6 -915
- package/runtime/sqlite/LICENSE.md +85 -0
- package/runtime/sqlite/README.md +45 -0
- package/runtime/sqlite/nish-sqlite.c +359 -0
- package/runtime/sqlite/sqlite3.c +271131 -0
- package/runtime/sqlite/sqlite3.h +14447 -0
- package/scripts/build.sh +437 -28
- package/std/LICENSE-fast-float +23 -0
- package/std/LICENSE-fdlibm +8 -0
- package/std/README.md +108 -58
- package/std/base64.ts +116 -0
- package/std/bytes.ts +419 -0
- package/std/cli.ts +328 -0
- package/std/collections.ts +6 -5
- package/std/crypto/LICENSE-crypt_blowfish +26 -0
- package/std/crypto/LICENSE-libsecp256k1 +19 -0
- package/std/crypto/aes.ts +238 -76
- package/std/crypto/base64url.ts +125 -0
- package/std/crypto/bcrypt.ts +760 -0
- package/std/crypto/chacha20poly1305.ts +43 -9
- package/std/crypto/ed25519.ts +840 -0
- package/std/crypto/hkdf.ts +304 -12
- package/std/crypto/hmac.ts +382 -54
- package/std/crypto/p256.ts +932 -61
- package/std/crypto/x25519.ts +55 -37
- package/std/crypto/x509-random.ts +71 -0
- package/std/crypto/x509.ts +20 -131
- package/std/json-tokenizer.ts +4237 -0
- package/std/json-typed.ts +1014 -0
- package/std/json-value.ts +1182 -0
- package/std/json.ts +1202 -34
- package/std/math.ts +608 -0
- package/std/net/buffer-pool.ts +151 -0
- package/std/net/hpack.ts +416 -112
- package/std/net/http-fields.ts +93 -0
- package/std/net/http1-server.ts +501 -62
- package/std/net/http1.ts +31 -6
- package/std/net/http2-tls.ts +112 -24
- package/std/net/http2.ts +333 -75
- package/std/net/http3-server.ts +261 -43
- package/std/net/http3.ts +187 -28
- package/std/net/postgres.ts +1875 -0
- package/std/net/prefork.ts +412 -0
- package/std/net/quic-listener.ts +108 -11
- package/std/net/quic-packet.ts +243 -82
- package/std/net/quic-recovery.ts +361 -30
- package/std/net/quic-stream.ts +424 -24
- package/std/net/quic.ts +519 -167
- package/std/net/router.ts +676 -0
- package/std/net/tls/codec.ts +426 -27
- package/std/net/tls/record-server.ts +478 -132
- package/std/net/tls/record.ts +133 -42
- package/std/net/tls/schedule.ts +464 -55
- package/std/net/tls/ticket.ts +610 -0
- package/std/net/tls-tcp.ts +196 -53
- package/std/net/tls.ts +1018 -260
- package/std/net/url.ts +571 -0
- package/std/random.ts +94 -0
- package/std/regex.ts +4274 -0
- package/std/sqlite.ts +391 -0
- package/std/term-io.ts +978 -0
- package/std/term-pixels.ts +874 -0
- package/std/term-probe.ts +459 -0
- package/std/term-rate.ts +993 -0
- package/std/term.ts +2453 -0
- package/std/text.ts +90 -7
- package/std/threads.ts +438 -14
- package/std/yaml-lines.ts +141 -0
- 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())
|