@effected/yaml 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,716 @@
1
+ import { lexAll } from "./lexer.js";
2
+
3
+ //#region src/internal/cst-parser.ts
4
+ /**
5
+ * Hardening: cap CST nesting so hostile inputs cannot overflow the stack
6
+ * while the tree is being BUILT. Set slightly above the composer's
7
+ * MAX_NESTING_DEPTH (256) so the composer's own guard — which reports the
8
+ * user-facing NestingDepthExceeded diagnostic — always fires first when the
9
+ * capped tree is composed.
10
+ */
11
+ const MAX_CST_DEPTH = 264;
12
+ /**
13
+ * When the depth budget is exhausted, consume the current token and return
14
+ * a flat error node instead of recursing. Returns null when within budget.
15
+ */
16
+ function guardDepth(state) {
17
+ if (state.depth < MAX_CST_DEPTH) return null;
18
+ const token = advance(state);
19
+ return {
20
+ type: "error",
21
+ source: token?.value ?? "",
22
+ offset: token?.offset ?? state.text.length,
23
+ length: token?.length ?? 0
24
+ };
25
+ }
26
+ function atEnd(state) {
27
+ return state.pos >= state.tokens.length;
28
+ }
29
+ function peek(state) {
30
+ return state.tokens[state.pos];
31
+ }
32
+ function advance(state) {
33
+ const token = state.tokens[state.pos];
34
+ state.pos++;
35
+ return token;
36
+ }
37
+ /** Check if the current token is a trivia token (whitespace, newline, comment). */
38
+ function isTrivia(token) {
39
+ return token.kind === "whitespace" || token.kind === "newline" || token.kind === "comment";
40
+ }
41
+ /** Check if the current token starts a new document boundary. */
42
+ function isDocumentBoundary(token) {
43
+ return token.kind === "document-start" || token.kind === "document-end";
44
+ }
45
+ /** Build a CstNode from collected children, computing source from the text. */
46
+ function makeContainerNode(type, children, text) {
47
+ if (children.length === 0) return {
48
+ type,
49
+ source: "",
50
+ offset: 0,
51
+ length: 0,
52
+ children
53
+ };
54
+ const first = children[0];
55
+ const last = children[children.length - 1];
56
+ const offset = first.offset;
57
+ const end = last.offset + last.length;
58
+ return {
59
+ type,
60
+ source: text.slice(offset, end),
61
+ offset,
62
+ length: end - offset,
63
+ children
64
+ };
65
+ }
66
+ /**
67
+ * Create a leaf CstNode from a token, using the raw source text.
68
+ *
69
+ * We slice from the original text rather than using `token.value` because
70
+ * the lexer decodes certain tokens (e.g. quoted scalars have quotes stripped
71
+ * and escape sequences resolved in `value`), but the CST must preserve
72
+ * the raw source text exactly as written.
73
+ */
74
+ function makeLeafNode(type, token, text) {
75
+ return {
76
+ type,
77
+ source: text.slice(token.offset, token.offset + token.length),
78
+ offset: token.offset,
79
+ length: token.length
80
+ };
81
+ }
82
+ /**
83
+ * Consume trivia tokens (whitespace, newline, comment) and return them as CST nodes.
84
+ */
85
+ function consumeTrivia(state) {
86
+ const nodes = [];
87
+ while (!atEnd(state)) {
88
+ const token = peek(state);
89
+ if (!token || !isTrivia(token)) break;
90
+ advance(state);
91
+ if (token.kind === "comment") nodes.push(makeLeafNode("comment", token, state.text));
92
+ else if (token.kind === "newline") nodes.push(makeLeafNode("newline", token, state.text));
93
+ else nodes.push(makeLeafNode("whitespace", token, state.text));
94
+ }
95
+ return nodes;
96
+ }
97
+ /**
98
+ * Consume a single trivia-or-content token and return it as a CST node.
99
+ * Used for tokens that don't form higher-level structures.
100
+ */
101
+ function consumeLeafToken(state) {
102
+ const token = peek(state);
103
+ if (!token) return void 0;
104
+ advance(state);
105
+ switch (token.kind) {
106
+ case "whitespace": return makeLeafNode("whitespace", token, state.text);
107
+ case "newline": return makeLeafNode("newline", token, state.text);
108
+ case "comment": return makeLeafNode("comment", token, state.text);
109
+ case "scalar": return makeLeafNode("flow-scalar", token, state.text);
110
+ case "anchor": return makeLeafNode("anchor", token, state.text);
111
+ case "alias": return makeLeafNode("alias", token, state.text);
112
+ case "tag": return makeLeafNode("tag", token, state.text);
113
+ case "directive": return makeLeafNode("directive", token, state.text);
114
+ case "flow-separator": return makeLeafNode("whitespace", token, state.text);
115
+ case "block-map-value":
116
+ case "block-map-key":
117
+ case "block-seq-entry": return makeLeafNode("whitespace", token, state.text);
118
+ case "document-start":
119
+ case "document-end": return makeLeafNode("whitespace", token, state.text);
120
+ case "flow-map-start":
121
+ case "flow-map-end":
122
+ case "flow-seq-start":
123
+ case "flow-seq-end": return makeLeafNode("whitespace", token, state.text);
124
+ case "block-map-start":
125
+ case "block-seq-start": return makeLeafNode("whitespace", token, state.text);
126
+ case "byte-order-mark": return makeLeafNode("whitespace", token, state.text);
127
+ case "error": return makeLeafNode("error", token, state.text);
128
+ default: return makeLeafNode("error", token, state.text);
129
+ }
130
+ }
131
+ /**
132
+ * Parse a flow mapping (curly braces).
133
+ */
134
+ function parseFlowMapping(state) {
135
+ const guarded = guardDepth(state);
136
+ if (guarded !== null) return guarded;
137
+ state.depth++;
138
+ try {
139
+ return parseFlowMappingInner(state);
140
+ } finally {
141
+ state.depth--;
142
+ }
143
+ }
144
+ function parseFlowMappingInner(state) {
145
+ const children = [];
146
+ const open = advance(state);
147
+ if (open) children.push(makeLeafNode("whitespace", open, state.text));
148
+ while (!atEnd(state)) {
149
+ const token = peek(state);
150
+ if (!token) break;
151
+ if (token.kind === "flow-map-end") {
152
+ const close = advance(state);
153
+ if (close) children.push(makeLeafNode("whitespace", close, state.text));
154
+ break;
155
+ }
156
+ if (token.kind === "flow-map-start") children.push(parseFlowMapping(state));
157
+ else if (token.kind === "flow-seq-start") children.push(parseFlowSequence(state));
158
+ else {
159
+ const leaf = consumeLeafToken(state);
160
+ if (leaf) children.push(leaf);
161
+ }
162
+ }
163
+ return makeContainerNode("flow-map", children, state.text);
164
+ }
165
+ /**
166
+ * Parse a flow sequence: [ ... ]
167
+ */
168
+ function parseFlowSequence(state) {
169
+ const guarded = guardDepth(state);
170
+ if (guarded !== null) return guarded;
171
+ state.depth++;
172
+ try {
173
+ return parseFlowSequenceInner(state);
174
+ } finally {
175
+ state.depth--;
176
+ }
177
+ }
178
+ function parseFlowSequenceInner(state) {
179
+ const children = [];
180
+ const open = advance(state);
181
+ if (open) children.push(makeLeafNode("whitespace", open, state.text));
182
+ while (!atEnd(state)) {
183
+ const token = peek(state);
184
+ if (!token) break;
185
+ if (token.kind === "flow-seq-end") {
186
+ const close = advance(state);
187
+ if (close) children.push(makeLeafNode("whitespace", close, state.text));
188
+ break;
189
+ }
190
+ if (token.kind === "flow-map-start") children.push(parseFlowMapping(state));
191
+ else if (token.kind === "flow-seq-start") children.push(parseFlowSequence(state));
192
+ else {
193
+ const leaf = consumeLeafToken(state);
194
+ if (leaf) children.push(leaf);
195
+ }
196
+ }
197
+ return makeContainerNode("flow-seq", children, state.text);
198
+ }
199
+ /**
200
+ * Parse a block scalar token. The lexer already handles the block scalar
201
+ * content (literal `|` or folded `\>`), so we just need to wrap it.
202
+ */
203
+ function parseBlockScalar(state) {
204
+ const token = advance(state);
205
+ if (!token) return {
206
+ type: "block-scalar",
207
+ source: "",
208
+ offset: 0,
209
+ length: 0
210
+ };
211
+ return {
212
+ type: "block-scalar",
213
+ source: state.text.slice(token.offset, token.offset + token.length),
214
+ offset: token.offset,
215
+ length: token.length
216
+ };
217
+ }
218
+ /**
219
+ * Check if the current position has a block scalar indicator in the original text.
220
+ */
221
+ function isBlockScalarToken(state) {
222
+ const token = peek(state);
223
+ if (token?.kind !== "scalar") return false;
224
+ const ch = state.text[token.offset];
225
+ return ch === "|" || ch === ">";
226
+ }
227
+ /**
228
+ * Check if the last non-trivia child in a CST node list is a value separator
229
+ * (`:`) with no subsequent value content. This indicates the next block
230
+ * structure should be consumed as the mapping value, not ejected as a sibling.
231
+ */
232
+ function lastNonTriviaIsValueSep(children) {
233
+ for (let i = children.length - 1; i >= 0; i--) {
234
+ const c = children[i];
235
+ if (!c) continue;
236
+ if (c.type === "whitespace" && c.source === ":") return true;
237
+ if (c.type === "newline" || c.type === "comment") continue;
238
+ if (c.type === "whitespace") continue;
239
+ if (c.type === "anchor" || c.type === "tag") continue;
240
+ return false;
241
+ }
242
+ return false;
243
+ }
244
+ /**
245
+ * Find the column of the first block-seq-entry in the token stream,
246
+ * skipping the zero-width block-seq-start marker and any trivia.
247
+ * Falls back to `fallback` if no entry is found.
248
+ */
249
+ function findFirstSeqEntryColumn(state, fallback) {
250
+ for (let i = state.pos; i < state.tokens.length; i++) {
251
+ const t = state.tokens[i];
252
+ if (!t) break;
253
+ if (t.kind === "block-seq-start") continue;
254
+ if (isTrivia(t)) continue;
255
+ if (t.kind === "block-seq-entry") return t.column;
256
+ break;
257
+ }
258
+ return fallback;
259
+ }
260
+ /**
261
+ * Parse a block mapping at the given indentation level.
262
+ */
263
+ function parseBlockMapping(state, indent) {
264
+ const guarded = guardDepth(state);
265
+ if (guarded !== null) return guarded;
266
+ state.depth++;
267
+ try {
268
+ return parseBlockMappingInner(state, indent);
269
+ } finally {
270
+ state.depth--;
271
+ }
272
+ }
273
+ function parseBlockMappingInner(state, indent) {
274
+ const children = [];
275
+ let sawExplicitKey = false;
276
+ if (peek(state)?.kind === "block-map-start") advance(state);
277
+ while (!atEnd(state)) {
278
+ const token = peek(state);
279
+ if (!token) break;
280
+ if (isDocumentBoundary(token)) break;
281
+ if (token.kind === "block-seq-start" && token.column <= indent && children.length > 0) {
282
+ if (!lastNonTriviaIsValueSep(children) && !sawExplicitKey) break;
283
+ }
284
+ if (isTrivia(token)) {
285
+ children.push(...consumeTrivia(state));
286
+ continue;
287
+ }
288
+ if (token.kind === "block-map-key") {
289
+ sawExplicitKey = true;
290
+ const leaf = consumeLeafToken(state);
291
+ if (leaf) children.push(leaf);
292
+ continue;
293
+ }
294
+ if (token.kind === "block-map-value") {
295
+ const leaf = consumeLeafToken(state);
296
+ if (leaf) children.push(leaf);
297
+ children.push(...parseBlockValue(state, indent, sawExplicitKey));
298
+ sawExplicitKey = false;
299
+ continue;
300
+ }
301
+ if (token.kind === "scalar" || token.kind === "anchor" || token.kind === "alias" || token.kind === "tag") {
302
+ if (token.column < indent && children.length > 0) break;
303
+ if (isBlockScalarToken(state)) {
304
+ children.push(parseBlockScalar(state));
305
+ continue;
306
+ }
307
+ const leaf = consumeLeafToken(state);
308
+ if (leaf) children.push(leaf);
309
+ continue;
310
+ }
311
+ if (token.kind === "block-map-start") {
312
+ if (token.column > indent) children.push(parseBlockMapping(state, token.column));
313
+ else if (token.column === indent) advance(state);
314
+ else break;
315
+ continue;
316
+ }
317
+ if (token.kind === "block-seq-start") {
318
+ const seqIndent = sawExplicitKey ? findFirstSeqEntryColumn(state, token.column) : token.column;
319
+ children.push(parseBlockSequence(state, seqIndent));
320
+ continue;
321
+ }
322
+ if (token.kind === "block-seq-entry") {
323
+ if (token.column === indent && lastNonTriviaIsValueSep(children)) {
324
+ const seqChildren = [];
325
+ while (!atEnd(state)) {
326
+ const seqToken = peek(state);
327
+ if (!seqToken) break;
328
+ if (seqToken.kind === "block-seq-entry" && seqToken.column === indent) {
329
+ seqChildren.push(...consumeTrivia(state));
330
+ const entry = consumeLeafToken(state);
331
+ if (entry) seqChildren.push(entry);
332
+ seqChildren.push(...parseSequenceEntryContent(state, indent));
333
+ } else if (isTrivia(seqToken)) seqChildren.push(...consumeTrivia(state));
334
+ else break;
335
+ }
336
+ if (seqChildren.length > 0) children.push(makeContainerNode("block-seq", seqChildren, state.text));
337
+ continue;
338
+ }
339
+ if (token.column <= indent) break;
340
+ const leaf = consumeLeafToken(state);
341
+ if (leaf) children.push(leaf);
342
+ continue;
343
+ }
344
+ if (token.kind === "flow-map-start") {
345
+ children.push(parseFlowMapping(state));
346
+ continue;
347
+ }
348
+ if (token.kind === "flow-seq-start") {
349
+ children.push(parseFlowSequence(state));
350
+ continue;
351
+ }
352
+ const leaf = consumeLeafToken(state);
353
+ if (leaf) children.push(leaf);
354
+ }
355
+ return makeContainerNode("block-map", children, state.text);
356
+ }
357
+ /**
358
+ * Parse the value part after a ":" in a block mapping.
359
+ */
360
+ function parseBlockValue(state, _parentIndent, explicitKey = false) {
361
+ const nodes = [];
362
+ while (!atEnd(state)) {
363
+ const token = peek(state);
364
+ if (!token) break;
365
+ if (token.kind === "whitespace") {
366
+ nodes.push(...consumeTrivia(state));
367
+ continue;
368
+ }
369
+ if (token.kind === "newline") break;
370
+ if (token.kind === "comment") break;
371
+ if (token.kind === "flow-map-start") {
372
+ nodes.push(parseFlowMapping(state));
373
+ break;
374
+ }
375
+ if (token.kind === "flow-seq-start") {
376
+ nodes.push(parseFlowSequence(state));
377
+ break;
378
+ }
379
+ if (isBlockScalarToken(state)) {
380
+ nodes.push(parseBlockScalar(state));
381
+ break;
382
+ }
383
+ if (explicitKey && (token.kind === "block-seq-start" || token.kind === "block-seq-entry")) {
384
+ const seqIndent = findFirstSeqEntryColumn(state, token.column);
385
+ nodes.push(parseBlockSequence(state, seqIndent));
386
+ break;
387
+ }
388
+ if (token.kind === "scalar" || token.kind === "anchor" || token.kind === "alias" || token.kind === "tag") {
389
+ const leaf = consumeLeafToken(state);
390
+ if (leaf) nodes.push(leaf);
391
+ continue;
392
+ }
393
+ break;
394
+ }
395
+ return nodes;
396
+ }
397
+ /**
398
+ * Parse a block sequence at the given indentation level.
399
+ */
400
+ function parseBlockSequence(state, indent) {
401
+ const guarded = guardDepth(state);
402
+ if (guarded !== null) return guarded;
403
+ state.depth++;
404
+ try {
405
+ return parseBlockSequenceInner(state, indent);
406
+ } finally {
407
+ state.depth--;
408
+ }
409
+ }
410
+ function parseBlockSequenceInner(state, indent) {
411
+ const children = [];
412
+ if (peek(state)?.kind === "block-seq-start") advance(state);
413
+ while (!atEnd(state)) {
414
+ const token = peek(state);
415
+ if (!token) break;
416
+ if (isDocumentBoundary(token)) break;
417
+ if (isTrivia(token)) {
418
+ children.push(...consumeTrivia(state));
419
+ continue;
420
+ }
421
+ if (token.kind === "block-seq-entry") {
422
+ if (token.column < indent) break;
423
+ if (token.column > indent) break;
424
+ const leaf = consumeLeafToken(state);
425
+ if (leaf) children.push(leaf);
426
+ children.push(...parseSequenceEntryContent(state, indent));
427
+ continue;
428
+ }
429
+ if (token.kind === "block-map-start" && token.column > indent) {
430
+ children.push(parseBlockMapping(state, token.column));
431
+ continue;
432
+ }
433
+ if (token.kind === "block-seq-start") {
434
+ if (token.column > indent) children.push(parseBlockSequence(state, token.column));
435
+ else advance(state);
436
+ continue;
437
+ }
438
+ break;
439
+ }
440
+ return makeContainerNode("block-seq", children, state.text);
441
+ }
442
+ /**
443
+ * Look ahead (without consuming) to see if a block-map-value token exists
444
+ * before the next newline / document boundary / sequence entry at this indent.
445
+ */
446
+ function hasImplicitMapAhead(state, seqIndent) {
447
+ let flowDepth = 0;
448
+ for (let i = state.pos; i < state.tokens.length; i++) {
449
+ const t = state.tokens[i];
450
+ if (!t) break;
451
+ if (t.kind === "flow-map-start" || t.kind === "flow-seq-start") {
452
+ flowDepth++;
453
+ continue;
454
+ }
455
+ if (t.kind === "flow-map-end" || t.kind === "flow-seq-end") {
456
+ flowDepth--;
457
+ continue;
458
+ }
459
+ if (flowDepth > 0) continue;
460
+ if (t.kind === "newline") return false;
461
+ if (isDocumentBoundary(t)) return false;
462
+ if (t.kind === "block-seq-entry" && t.column <= seqIndent) return false;
463
+ if (t.kind === "block-map-value") return true;
464
+ }
465
+ return false;
466
+ }
467
+ /**
468
+ * Parse the content of a sequence entry (after the `-`).
469
+ */
470
+ function parseSequenceEntryContent(state, seqIndent) {
471
+ const nodes = [];
472
+ const nextToken = findNextNonTrivia(state);
473
+ if (nextToken && nextToken.kind === "block-seq-entry" && nextToken.column > seqIndent) {} else if (hasImplicitMapAhead(state, seqIndent)) {
474
+ nodes.push(parseImplicitBlockMapping(state, seqIndent));
475
+ return nodes;
476
+ }
477
+ while (!atEnd(state)) {
478
+ const token = peek(state);
479
+ if (!token) break;
480
+ if (isDocumentBoundary(token)) break;
481
+ if (token.kind === "block-seq-entry" && token.column <= seqIndent) break;
482
+ if (token.kind === "block-seq-entry" && token.column > seqIndent) {
483
+ nodes.push(parseBlockSequence(state, token.column));
484
+ continue;
485
+ }
486
+ if (isTrivia(token)) {
487
+ nodes.push(...consumeTrivia(state));
488
+ continue;
489
+ }
490
+ if (token.kind === "block-map-start") {
491
+ nodes.push(parseBlockMapping(state, token.column));
492
+ continue;
493
+ }
494
+ if (token.kind === "block-seq-start") {
495
+ if (token.column <= seqIndent) break;
496
+ nodes.push(parseBlockSequence(state, token.column));
497
+ continue;
498
+ }
499
+ if (token.kind === "flow-map-start") {
500
+ nodes.push(parseFlowMapping(state));
501
+ continue;
502
+ }
503
+ if (token.kind === "flow-seq-start") {
504
+ nodes.push(parseFlowSequence(state));
505
+ continue;
506
+ }
507
+ if (isBlockScalarToken(state)) {
508
+ nodes.push(parseBlockScalar(state));
509
+ continue;
510
+ }
511
+ if (token.kind === "scalar" || token.kind === "block-map-value" || token.kind === "block-map-key" || token.kind === "anchor" || token.kind === "alias" || token.kind === "tag") {
512
+ if (token.column <= seqIndent) break;
513
+ const leaf = consumeLeafToken(state);
514
+ if (leaf) nodes.push(leaf);
515
+ continue;
516
+ }
517
+ break;
518
+ }
519
+ return nodes;
520
+ }
521
+ /**
522
+ * Parse an implicit block mapping (no block-map-start token from the lexer).
523
+ * This occurs inside sequence entries like `- a: 1`.
524
+ */
525
+ function parseImplicitBlockMapping(state, seqIndent) {
526
+ const guarded = guardDepth(state);
527
+ if (guarded !== null) return guarded;
528
+ state.depth++;
529
+ try {
530
+ return parseImplicitBlockMappingInner(state, seqIndent);
531
+ } finally {
532
+ state.depth--;
533
+ }
534
+ }
535
+ function parseImplicitBlockMappingInner(state, seqIndent) {
536
+ const children = [];
537
+ let entryIndent = -1;
538
+ while (!atEnd(state)) {
539
+ const token = peek(state);
540
+ if (!token) break;
541
+ if (isDocumentBoundary(token)) break;
542
+ if (token.kind === "block-seq-entry" && token.column <= seqIndent) break;
543
+ if (isTrivia(token)) {
544
+ children.push(...consumeTrivia(state));
545
+ continue;
546
+ }
547
+ if (token.kind === "block-map-value") {
548
+ const leaf = consumeLeafToken(state);
549
+ if (leaf) children.push(leaf);
550
+ children.push(...parseBlockValue(state, seqIndent));
551
+ continue;
552
+ }
553
+ if (token.kind === "scalar" || token.kind === "anchor" || token.kind === "alias" || token.kind === "tag") {
554
+ if (isBlockScalarToken(state)) {
555
+ children.push(parseBlockScalar(state));
556
+ continue;
557
+ }
558
+ if (entryIndent >= 0 && token.column <= seqIndent) break;
559
+ if (entryIndent < 0) entryIndent = token.column;
560
+ const leaf = consumeLeafToken(state);
561
+ if (leaf) children.push(leaf);
562
+ continue;
563
+ }
564
+ if (token.kind === "block-map-start") {
565
+ if (entryIndent >= 0 && token.column <= entryIndent) {
566
+ advance(state);
567
+ continue;
568
+ }
569
+ children.push(parseBlockMapping(state, token.column));
570
+ continue;
571
+ }
572
+ if (token.kind === "block-seq-start") {
573
+ if (token.column <= seqIndent && children.length > 0) break;
574
+ children.push(parseBlockSequence(state, token.column));
575
+ continue;
576
+ }
577
+ if (token.kind === "flow-map-start") {
578
+ children.push(parseFlowMapping(state));
579
+ continue;
580
+ }
581
+ if (token.kind === "flow-seq-start") {
582
+ children.push(parseFlowSequence(state));
583
+ continue;
584
+ }
585
+ const leaf = consumeLeafToken(state);
586
+ if (leaf) children.push(leaf);
587
+ }
588
+ return makeContainerNode("block-map", children, state.text);
589
+ }
590
+ /**
591
+ * Parse a single document from the token stream.
592
+ */
593
+ function parseDocument(state) {
594
+ const children = [];
595
+ while (!atEnd(state)) {
596
+ const token = peek(state);
597
+ if (!token) break;
598
+ if (token.kind === "directive") {
599
+ const leaf = consumeLeafToken(state);
600
+ if (leaf) children.push(leaf);
601
+ continue;
602
+ }
603
+ if (isTrivia(token) && token.kind !== "comment") {
604
+ children.push(...consumeTrivia(state));
605
+ continue;
606
+ }
607
+ if (token.kind === "comment") {
608
+ const nextNonTrivia = findNextNonTrivia(state);
609
+ if (nextNonTrivia?.kind === "directive" || nextNonTrivia?.kind === "document-start") {
610
+ children.push(...consumeTrivia(state));
611
+ continue;
612
+ }
613
+ break;
614
+ }
615
+ break;
616
+ }
617
+ if (!atEnd(state)) {
618
+ if (peek(state)?.kind === "document-start") {
619
+ const leaf = consumeLeafToken(state);
620
+ if (leaf) children.push(leaf);
621
+ }
622
+ }
623
+ while (!atEnd(state)) {
624
+ const token = peek(state);
625
+ if (!token) break;
626
+ if (token.kind === "document-start") break;
627
+ if (token.kind === "document-end") {
628
+ const leaf = consumeLeafToken(state);
629
+ if (leaf) children.push(leaf);
630
+ while (!atEnd(state)) {
631
+ const t = peek(state);
632
+ if (!t) break;
633
+ if (t.kind === "newline" || t.kind === "whitespace" || t.kind === "comment") children.push(...consumeTrivia(state));
634
+ else break;
635
+ }
636
+ break;
637
+ }
638
+ if (isTrivia(token)) {
639
+ children.push(...consumeTrivia(state));
640
+ continue;
641
+ }
642
+ if (token.kind === "block-map-start") {
643
+ children.push(parseBlockMapping(state, token.column));
644
+ continue;
645
+ }
646
+ if (token.kind === "block-seq-start") {
647
+ children.push(parseBlockSequence(state, token.column));
648
+ continue;
649
+ }
650
+ if (token.kind === "flow-map-start") {
651
+ children.push(parseFlowMapping(state));
652
+ continue;
653
+ }
654
+ if (token.kind === "flow-seq-start") {
655
+ children.push(parseFlowSequence(state));
656
+ continue;
657
+ }
658
+ if (isBlockScalarToken(state)) {
659
+ children.push(parseBlockScalar(state));
660
+ continue;
661
+ }
662
+ const leaf = consumeLeafToken(state);
663
+ if (leaf) children.push(leaf);
664
+ }
665
+ return makeContainerNode("document", children, state.text);
666
+ }
667
+ /**
668
+ * Find the next non-trivia token without consuming.
669
+ */
670
+ function findNextNonTrivia(state) {
671
+ for (let i = state.pos; i < state.tokens.length; i++) {
672
+ const t = state.tokens[i];
673
+ if (t && !isTrivia(t)) return t;
674
+ }
675
+ }
676
+ /**
677
+ * Parse all documents from the token stream.
678
+ */
679
+ function parseDocuments(tokens, text) {
680
+ const state = {
681
+ tokens,
682
+ text,
683
+ pos: 0,
684
+ depth: 0
685
+ };
686
+ const documents = [];
687
+ if (atEnd(state)) return [{
688
+ type: "document",
689
+ source: "",
690
+ offset: 0,
691
+ length: 0,
692
+ children: []
693
+ }];
694
+ while (!atEnd(state)) {
695
+ const before = state.pos;
696
+ const doc = parseDocument(state);
697
+ const hasDocStart = doc.children?.some((c) => c.type === "whitespace" && c.source === "---");
698
+ const hasContent = doc.children?.some((c) => c.type !== "whitespace" && c.type !== "newline" && c.type !== "comment" && c.type !== "error");
699
+ if (hasDocStart || hasContent || documents.length === 0) documents.push(doc);
700
+ if (state.pos === before && !atEnd(state)) state.pos++;
701
+ }
702
+ return documents;
703
+ }
704
+ /**
705
+ * Parse YAML source text and collect all CST document nodes into an array.
706
+ *
707
+ * Each CST node preserves every character of the original input, including
708
+ * whitespace, comments, and structural indicators. No value interpretation
709
+ * occurs at this stage — `true` is still the string `"true"`.
710
+ */
711
+ function parseCSTAll(text) {
712
+ return parseDocuments(lexAll(text), text);
713
+ }
714
+
715
+ //#endregion
716
+ export { parseCSTAll };