1 | 'use strict';
|
---|
2 |
|
---|
3 | var cst = require('./cst.js');
|
---|
4 | var lexer = require('./lexer.js');
|
---|
5 |
|
---|
6 | function includesToken(list, type) {
|
---|
7 | for (let i = 0; i < list.length; ++i)
|
---|
8 | if (list[i].type === type)
|
---|
9 | return true;
|
---|
10 | return false;
|
---|
11 | }
|
---|
12 | function findNonEmptyIndex(list) {
|
---|
13 | for (let i = 0; i < list.length; ++i) {
|
---|
14 | switch (list[i].type) {
|
---|
15 | case 'space':
|
---|
16 | case 'comment':
|
---|
17 | case 'newline':
|
---|
18 | break;
|
---|
19 | default:
|
---|
20 | return i;
|
---|
21 | }
|
---|
22 | }
|
---|
23 | return -1;
|
---|
24 | }
|
---|
25 | function isFlowToken(token) {
|
---|
26 | switch (token?.type) {
|
---|
27 | case 'alias':
|
---|
28 | case 'scalar':
|
---|
29 | case 'single-quoted-scalar':
|
---|
30 | case 'double-quoted-scalar':
|
---|
31 | case 'flow-collection':
|
---|
32 | return true;
|
---|
33 | default:
|
---|
34 | return false;
|
---|
35 | }
|
---|
36 | }
|
---|
37 | function getPrevProps(parent) {
|
---|
38 | switch (parent.type) {
|
---|
39 | case 'document':
|
---|
40 | return parent.start;
|
---|
41 | case 'block-map': {
|
---|
42 | const it = parent.items[parent.items.length - 1];
|
---|
43 | return it.sep ?? it.start;
|
---|
44 | }
|
---|
45 | case 'block-seq':
|
---|
46 | return parent.items[parent.items.length - 1].start;
|
---|
47 | /* istanbul ignore next should not happen */
|
---|
48 | default:
|
---|
49 | return [];
|
---|
50 | }
|
---|
51 | }
|
---|
52 | /** Note: May modify input array */
|
---|
53 | function getFirstKeyStartProps(prev) {
|
---|
54 | if (prev.length === 0)
|
---|
55 | return [];
|
---|
56 | let i = prev.length;
|
---|
57 | loop: while (--i >= 0) {
|
---|
58 | switch (prev[i].type) {
|
---|
59 | case 'doc-start':
|
---|
60 | case 'explicit-key-ind':
|
---|
61 | case 'map-value-ind':
|
---|
62 | case 'seq-item-ind':
|
---|
63 | case 'newline':
|
---|
64 | break loop;
|
---|
65 | }
|
---|
66 | }
|
---|
67 | while (prev[++i]?.type === 'space') {
|
---|
68 | /* loop */
|
---|
69 | }
|
---|
70 | return prev.splice(i, prev.length);
|
---|
71 | }
|
---|
72 | function fixFlowSeqItems(fc) {
|
---|
73 | if (fc.start.type === 'flow-seq-start') {
|
---|
74 | for (const it of fc.items) {
|
---|
75 | if (it.sep &&
|
---|
76 | !it.value &&
|
---|
77 | !includesToken(it.start, 'explicit-key-ind') &&
|
---|
78 | !includesToken(it.sep, 'map-value-ind')) {
|
---|
79 | if (it.key)
|
---|
80 | it.value = it.key;
|
---|
81 | delete it.key;
|
---|
82 | if (isFlowToken(it.value)) {
|
---|
83 | if (it.value.end)
|
---|
84 | Array.prototype.push.apply(it.value.end, it.sep);
|
---|
85 | else
|
---|
86 | it.value.end = it.sep;
|
---|
87 | }
|
---|
88 | else
|
---|
89 | Array.prototype.push.apply(it.start, it.sep);
|
---|
90 | delete it.sep;
|
---|
91 | }
|
---|
92 | }
|
---|
93 | }
|
---|
94 | }
|
---|
95 | /**
|
---|
96 | * A YAML concrete syntax tree (CST) parser
|
---|
97 | *
|
---|
98 | * ```ts
|
---|
99 | * const src: string = ...
|
---|
100 | * for (const token of new Parser().parse(src)) {
|
---|
101 | * // token: Token
|
---|
102 | * }
|
---|
103 | * ```
|
---|
104 | *
|
---|
105 | * To use the parser with a user-provided lexer:
|
---|
106 | *
|
---|
107 | * ```ts
|
---|
108 | * function* parse(source: string, lexer: Lexer) {
|
---|
109 | * const parser = new Parser()
|
---|
110 | * for (const lexeme of lexer.lex(source))
|
---|
111 | * yield* parser.next(lexeme)
|
---|
112 | * yield* parser.end()
|
---|
113 | * }
|
---|
114 | *
|
---|
115 | * const src: string = ...
|
---|
116 | * const lexer = new Lexer()
|
---|
117 | * for (const token of parse(src, lexer)) {
|
---|
118 | * // token: Token
|
---|
119 | * }
|
---|
120 | * ```
|
---|
121 | */
|
---|
122 | class Parser {
|
---|
123 | /**
|
---|
124 | * @param onNewLine - If defined, called separately with the start position of
|
---|
125 | * each new line (in `parse()`, including the start of input).
|
---|
126 | */
|
---|
127 | constructor(onNewLine) {
|
---|
128 | /** If true, space and sequence indicators count as indentation */
|
---|
129 | this.atNewLine = true;
|
---|
130 | /** If true, next token is a scalar value */
|
---|
131 | this.atScalar = false;
|
---|
132 | /** Current indentation level */
|
---|
133 | this.indent = 0;
|
---|
134 | /** Current offset since the start of parsing */
|
---|
135 | this.offset = 0;
|
---|
136 | /** On the same line with a block map key */
|
---|
137 | this.onKeyLine = false;
|
---|
138 | /** Top indicates the node that's currently being built */
|
---|
139 | this.stack = [];
|
---|
140 | /** The source of the current token, set in parse() */
|
---|
141 | this.source = '';
|
---|
142 | /** The type of the current token, set in parse() */
|
---|
143 | this.type = '';
|
---|
144 | // Must be defined after `next()`
|
---|
145 | this.lexer = new lexer.Lexer();
|
---|
146 | this.onNewLine = onNewLine;
|
---|
147 | }
|
---|
148 | /**
|
---|
149 | * Parse `source` as a YAML stream.
|
---|
150 | * If `incomplete`, a part of the last line may be left as a buffer for the next call.
|
---|
151 | *
|
---|
152 | * Errors are not thrown, but yielded as `{ type: 'error', message }` tokens.
|
---|
153 | *
|
---|
154 | * @returns A generator of tokens representing each directive, document, and other structure.
|
---|
155 | */
|
---|
156 | *parse(source, incomplete = false) {
|
---|
157 | if (this.onNewLine && this.offset === 0)
|
---|
158 | this.onNewLine(0);
|
---|
159 | for (const lexeme of this.lexer.lex(source, incomplete))
|
---|
160 | yield* this.next(lexeme);
|
---|
161 | if (!incomplete)
|
---|
162 | yield* this.end();
|
---|
163 | }
|
---|
164 | /**
|
---|
165 | * Advance the parser by the `source` of one lexical token.
|
---|
166 | */
|
---|
167 | *next(source) {
|
---|
168 | this.source = source;
|
---|
169 | if (process.env.LOG_TOKENS)
|
---|
170 | console.log('|', cst.prettyToken(source));
|
---|
171 | if (this.atScalar) {
|
---|
172 | this.atScalar = false;
|
---|
173 | yield* this.step();
|
---|
174 | this.offset += source.length;
|
---|
175 | return;
|
---|
176 | }
|
---|
177 | const type = cst.tokenType(source);
|
---|
178 | if (!type) {
|
---|
179 | const message = `Not a YAML token: ${source}`;
|
---|
180 | yield* this.pop({ type: 'error', offset: this.offset, message, source });
|
---|
181 | this.offset += source.length;
|
---|
182 | }
|
---|
183 | else if (type === 'scalar') {
|
---|
184 | this.atNewLine = false;
|
---|
185 | this.atScalar = true;
|
---|
186 | this.type = 'scalar';
|
---|
187 | }
|
---|
188 | else {
|
---|
189 | this.type = type;
|
---|
190 | yield* this.step();
|
---|
191 | switch (type) {
|
---|
192 | case 'newline':
|
---|
193 | this.atNewLine = true;
|
---|
194 | this.indent = 0;
|
---|
195 | if (this.onNewLine)
|
---|
196 | this.onNewLine(this.offset + source.length);
|
---|
197 | break;
|
---|
198 | case 'space':
|
---|
199 | if (this.atNewLine && source[0] === ' ')
|
---|
200 | this.indent += source.length;
|
---|
201 | break;
|
---|
202 | case 'explicit-key-ind':
|
---|
203 | case 'map-value-ind':
|
---|
204 | case 'seq-item-ind':
|
---|
205 | if (this.atNewLine)
|
---|
206 | this.indent += source.length;
|
---|
207 | break;
|
---|
208 | case 'doc-mode':
|
---|
209 | case 'flow-error-end':
|
---|
210 | return;
|
---|
211 | default:
|
---|
212 | this.atNewLine = false;
|
---|
213 | }
|
---|
214 | this.offset += source.length;
|
---|
215 | }
|
---|
216 | }
|
---|
217 | /** Call at end of input to push out any remaining constructions */
|
---|
218 | *end() {
|
---|
219 | while (this.stack.length > 0)
|
---|
220 | yield* this.pop();
|
---|
221 | }
|
---|
222 | get sourceToken() {
|
---|
223 | const st = {
|
---|
224 | type: this.type,
|
---|
225 | offset: this.offset,
|
---|
226 | indent: this.indent,
|
---|
227 | source: this.source
|
---|
228 | };
|
---|
229 | return st;
|
---|
230 | }
|
---|
231 | *step() {
|
---|
232 | const top = this.peek(1);
|
---|
233 | if (this.type === 'doc-end' && (!top || top.type !== 'doc-end')) {
|
---|
234 | while (this.stack.length > 0)
|
---|
235 | yield* this.pop();
|
---|
236 | this.stack.push({
|
---|
237 | type: 'doc-end',
|
---|
238 | offset: this.offset,
|
---|
239 | source: this.source
|
---|
240 | });
|
---|
241 | return;
|
---|
242 | }
|
---|
243 | if (!top)
|
---|
244 | return yield* this.stream();
|
---|
245 | switch (top.type) {
|
---|
246 | case 'document':
|
---|
247 | return yield* this.document(top);
|
---|
248 | case 'alias':
|
---|
249 | case 'scalar':
|
---|
250 | case 'single-quoted-scalar':
|
---|
251 | case 'double-quoted-scalar':
|
---|
252 | return yield* this.scalar(top);
|
---|
253 | case 'block-scalar':
|
---|
254 | return yield* this.blockScalar(top);
|
---|
255 | case 'block-map':
|
---|
256 | return yield* this.blockMap(top);
|
---|
257 | case 'block-seq':
|
---|
258 | return yield* this.blockSequence(top);
|
---|
259 | case 'flow-collection':
|
---|
260 | return yield* this.flowCollection(top);
|
---|
261 | case 'doc-end':
|
---|
262 | return yield* this.documentEnd(top);
|
---|
263 | }
|
---|
264 | /* istanbul ignore next should not happen */
|
---|
265 | yield* this.pop();
|
---|
266 | }
|
---|
267 | peek(n) {
|
---|
268 | return this.stack[this.stack.length - n];
|
---|
269 | }
|
---|
270 | *pop(error) {
|
---|
271 | const token = error ?? this.stack.pop();
|
---|
272 | /* istanbul ignore if should not happen */
|
---|
273 | if (!token) {
|
---|
274 | const message = 'Tried to pop an empty stack';
|
---|
275 | yield { type: 'error', offset: this.offset, source: '', message };
|
---|
276 | }
|
---|
277 | else if (this.stack.length === 0) {
|
---|
278 | yield token;
|
---|
279 | }
|
---|
280 | else {
|
---|
281 | const top = this.peek(1);
|
---|
282 | if (token.type === 'block-scalar') {
|
---|
283 | // Block scalars use their parent rather than header indent
|
---|
284 | token.indent = 'indent' in top ? top.indent : 0;
|
---|
285 | }
|
---|
286 | else if (token.type === 'flow-collection' && top.type === 'document') {
|
---|
287 | // Ignore all indent for top-level flow collections
|
---|
288 | token.indent = 0;
|
---|
289 | }
|
---|
290 | if (token.type === 'flow-collection')
|
---|
291 | fixFlowSeqItems(token);
|
---|
292 | switch (top.type) {
|
---|
293 | case 'document':
|
---|
294 | top.value = token;
|
---|
295 | break;
|
---|
296 | case 'block-scalar':
|
---|
297 | top.props.push(token); // error
|
---|
298 | break;
|
---|
299 | case 'block-map': {
|
---|
300 | const it = top.items[top.items.length - 1];
|
---|
301 | if (it.value) {
|
---|
302 | top.items.push({ start: [], key: token, sep: [] });
|
---|
303 | this.onKeyLine = true;
|
---|
304 | return;
|
---|
305 | }
|
---|
306 | else if (it.sep) {
|
---|
307 | it.value = token;
|
---|
308 | }
|
---|
309 | else {
|
---|
310 | Object.assign(it, { key: token, sep: [] });
|
---|
311 | this.onKeyLine = !includesToken(it.start, 'explicit-key-ind');
|
---|
312 | return;
|
---|
313 | }
|
---|
314 | break;
|
---|
315 | }
|
---|
316 | case 'block-seq': {
|
---|
317 | const it = top.items[top.items.length - 1];
|
---|
318 | if (it.value)
|
---|
319 | top.items.push({ start: [], value: token });
|
---|
320 | else
|
---|
321 | it.value = token;
|
---|
322 | break;
|
---|
323 | }
|
---|
324 | case 'flow-collection': {
|
---|
325 | const it = top.items[top.items.length - 1];
|
---|
326 | if (!it || it.value)
|
---|
327 | top.items.push({ start: [], key: token, sep: [] });
|
---|
328 | else if (it.sep)
|
---|
329 | it.value = token;
|
---|
330 | else
|
---|
331 | Object.assign(it, { key: token, sep: [] });
|
---|
332 | return;
|
---|
333 | }
|
---|
334 | /* istanbul ignore next should not happen */
|
---|
335 | default:
|
---|
336 | yield* this.pop();
|
---|
337 | yield* this.pop(token);
|
---|
338 | }
|
---|
339 | if ((top.type === 'document' ||
|
---|
340 | top.type === 'block-map' ||
|
---|
341 | top.type === 'block-seq') &&
|
---|
342 | (token.type === 'block-map' || token.type === 'block-seq')) {
|
---|
343 | const last = token.items[token.items.length - 1];
|
---|
344 | if (last &&
|
---|
345 | !last.sep &&
|
---|
346 | !last.value &&
|
---|
347 | last.start.length > 0 &&
|
---|
348 | findNonEmptyIndex(last.start) === -1 &&
|
---|
349 | (token.indent === 0 ||
|
---|
350 | last.start.every(st => st.type !== 'comment' || st.indent < token.indent))) {
|
---|
351 | if (top.type === 'document')
|
---|
352 | top.end = last.start;
|
---|
353 | else
|
---|
354 | top.items.push({ start: last.start });
|
---|
355 | token.items.splice(-1, 1);
|
---|
356 | }
|
---|
357 | }
|
---|
358 | }
|
---|
359 | }
|
---|
360 | *stream() {
|
---|
361 | switch (this.type) {
|
---|
362 | case 'directive-line':
|
---|
363 | yield { type: 'directive', offset: this.offset, source: this.source };
|
---|
364 | return;
|
---|
365 | case 'byte-order-mark':
|
---|
366 | case 'space':
|
---|
367 | case 'comment':
|
---|
368 | case 'newline':
|
---|
369 | yield this.sourceToken;
|
---|
370 | return;
|
---|
371 | case 'doc-mode':
|
---|
372 | case 'doc-start': {
|
---|
373 | const doc = {
|
---|
374 | type: 'document',
|
---|
375 | offset: this.offset,
|
---|
376 | start: []
|
---|
377 | };
|
---|
378 | if (this.type === 'doc-start')
|
---|
379 | doc.start.push(this.sourceToken);
|
---|
380 | this.stack.push(doc);
|
---|
381 | return;
|
---|
382 | }
|
---|
383 | }
|
---|
384 | yield {
|
---|
385 | type: 'error',
|
---|
386 | offset: this.offset,
|
---|
387 | message: `Unexpected ${this.type} token in YAML stream`,
|
---|
388 | source: this.source
|
---|
389 | };
|
---|
390 | }
|
---|
391 | *document(doc) {
|
---|
392 | if (doc.value)
|
---|
393 | return yield* this.lineEnd(doc);
|
---|
394 | switch (this.type) {
|
---|
395 | case 'doc-start': {
|
---|
396 | if (findNonEmptyIndex(doc.start) !== -1) {
|
---|
397 | yield* this.pop();
|
---|
398 | yield* this.step();
|
---|
399 | }
|
---|
400 | else
|
---|
401 | doc.start.push(this.sourceToken);
|
---|
402 | return;
|
---|
403 | }
|
---|
404 | case 'anchor':
|
---|
405 | case 'tag':
|
---|
406 | case 'space':
|
---|
407 | case 'comment':
|
---|
408 | case 'newline':
|
---|
409 | doc.start.push(this.sourceToken);
|
---|
410 | return;
|
---|
411 | }
|
---|
412 | const bv = this.startBlockValue(doc);
|
---|
413 | if (bv)
|
---|
414 | this.stack.push(bv);
|
---|
415 | else {
|
---|
416 | yield {
|
---|
417 | type: 'error',
|
---|
418 | offset: this.offset,
|
---|
419 | message: `Unexpected ${this.type} token in YAML document`,
|
---|
420 | source: this.source
|
---|
421 | };
|
---|
422 | }
|
---|
423 | }
|
---|
424 | *scalar(scalar) {
|
---|
425 | if (this.type === 'map-value-ind') {
|
---|
426 | const prev = getPrevProps(this.peek(2));
|
---|
427 | const start = getFirstKeyStartProps(prev);
|
---|
428 | let sep;
|
---|
429 | if (scalar.end) {
|
---|
430 | sep = scalar.end;
|
---|
431 | sep.push(this.sourceToken);
|
---|
432 | delete scalar.end;
|
---|
433 | }
|
---|
434 | else
|
---|
435 | sep = [this.sourceToken];
|
---|
436 | const map = {
|
---|
437 | type: 'block-map',
|
---|
438 | offset: scalar.offset,
|
---|
439 | indent: scalar.indent,
|
---|
440 | items: [{ start, key: scalar, sep }]
|
---|
441 | };
|
---|
442 | this.onKeyLine = true;
|
---|
443 | this.stack[this.stack.length - 1] = map;
|
---|
444 | }
|
---|
445 | else
|
---|
446 | yield* this.lineEnd(scalar);
|
---|
447 | }
|
---|
448 | *blockScalar(scalar) {
|
---|
449 | switch (this.type) {
|
---|
450 | case 'space':
|
---|
451 | case 'comment':
|
---|
452 | case 'newline':
|
---|
453 | scalar.props.push(this.sourceToken);
|
---|
454 | return;
|
---|
455 | case 'scalar':
|
---|
456 | scalar.source = this.source;
|
---|
457 | // block-scalar source includes trailing newline
|
---|
458 | this.atNewLine = true;
|
---|
459 | this.indent = 0;
|
---|
460 | if (this.onNewLine) {
|
---|
461 | let nl = this.source.indexOf('\n') + 1;
|
---|
462 | while (nl !== 0) {
|
---|
463 | this.onNewLine(this.offset + nl);
|
---|
464 | nl = this.source.indexOf('\n', nl) + 1;
|
---|
465 | }
|
---|
466 | }
|
---|
467 | yield* this.pop();
|
---|
468 | break;
|
---|
469 | /* istanbul ignore next should not happen */
|
---|
470 | default:
|
---|
471 | yield* this.pop();
|
---|
472 | yield* this.step();
|
---|
473 | }
|
---|
474 | }
|
---|
475 | *blockMap(map) {
|
---|
476 | const it = map.items[map.items.length - 1];
|
---|
477 | // it.sep is true-ish if pair already has key or : separator
|
---|
478 | switch (this.type) {
|
---|
479 | case 'newline':
|
---|
480 | this.onKeyLine = false;
|
---|
481 | if (it.value) {
|
---|
482 | const end = 'end' in it.value ? it.value.end : undefined;
|
---|
483 | const last = Array.isArray(end) ? end[end.length - 1] : undefined;
|
---|
484 | if (last?.type === 'comment')
|
---|
485 | end?.push(this.sourceToken);
|
---|
486 | else
|
---|
487 | map.items.push({ start: [this.sourceToken] });
|
---|
488 | }
|
---|
489 | else if (it.sep) {
|
---|
490 | it.sep.push(this.sourceToken);
|
---|
491 | }
|
---|
492 | else {
|
---|
493 | it.start.push(this.sourceToken);
|
---|
494 | }
|
---|
495 | return;
|
---|
496 | case 'space':
|
---|
497 | case 'comment':
|
---|
498 | if (it.value) {
|
---|
499 | map.items.push({ start: [this.sourceToken] });
|
---|
500 | }
|
---|
501 | else if (it.sep) {
|
---|
502 | it.sep.push(this.sourceToken);
|
---|
503 | }
|
---|
504 | else {
|
---|
505 | if (this.atIndentedComment(it.start, map.indent)) {
|
---|
506 | const prev = map.items[map.items.length - 2];
|
---|
507 | const end = prev?.value?.end;
|
---|
508 | if (Array.isArray(end)) {
|
---|
509 | Array.prototype.push.apply(end, it.start);
|
---|
510 | end.push(this.sourceToken);
|
---|
511 | map.items.pop();
|
---|
512 | return;
|
---|
513 | }
|
---|
514 | }
|
---|
515 | it.start.push(this.sourceToken);
|
---|
516 | }
|
---|
517 | return;
|
---|
518 | }
|
---|
519 | if (this.indent >= map.indent) {
|
---|
520 | const atNextItem = !this.onKeyLine && this.indent === map.indent && it.sep;
|
---|
521 | // For empty nodes, assign newline-separated not indented empty tokens to following node
|
---|
522 | let start = [];
|
---|
523 | if (atNextItem && it.sep && !it.value) {
|
---|
524 | const nl = [];
|
---|
525 | for (let i = 0; i < it.sep.length; ++i) {
|
---|
526 | const st = it.sep[i];
|
---|
527 | switch (st.type) {
|
---|
528 | case 'newline':
|
---|
529 | nl.push(i);
|
---|
530 | break;
|
---|
531 | case 'space':
|
---|
532 | break;
|
---|
533 | case 'comment':
|
---|
534 | if (st.indent > map.indent)
|
---|
535 | nl.length = 0;
|
---|
536 | break;
|
---|
537 | default:
|
---|
538 | nl.length = 0;
|
---|
539 | }
|
---|
540 | }
|
---|
541 | if (nl.length >= 2)
|
---|
542 | start = it.sep.splice(nl[1]);
|
---|
543 | }
|
---|
544 | switch (this.type) {
|
---|
545 | case 'anchor':
|
---|
546 | case 'tag':
|
---|
547 | if (atNextItem || it.value) {
|
---|
548 | start.push(this.sourceToken);
|
---|
549 | map.items.push({ start });
|
---|
550 | this.onKeyLine = true;
|
---|
551 | }
|
---|
552 | else if (it.sep) {
|
---|
553 | it.sep.push(this.sourceToken);
|
---|
554 | }
|
---|
555 | else {
|
---|
556 | it.start.push(this.sourceToken);
|
---|
557 | }
|
---|
558 | return;
|
---|
559 | case 'explicit-key-ind':
|
---|
560 | if (!it.sep && !includesToken(it.start, 'explicit-key-ind')) {
|
---|
561 | it.start.push(this.sourceToken);
|
---|
562 | }
|
---|
563 | else if (atNextItem || it.value) {
|
---|
564 | start.push(this.sourceToken);
|
---|
565 | map.items.push({ start });
|
---|
566 | }
|
---|
567 | else {
|
---|
568 | this.stack.push({
|
---|
569 | type: 'block-map',
|
---|
570 | offset: this.offset,
|
---|
571 | indent: this.indent,
|
---|
572 | items: [{ start: [this.sourceToken] }]
|
---|
573 | });
|
---|
574 | }
|
---|
575 | this.onKeyLine = true;
|
---|
576 | return;
|
---|
577 | case 'map-value-ind':
|
---|
578 | if (includesToken(it.start, 'explicit-key-ind')) {
|
---|
579 | if (!it.sep) {
|
---|
580 | if (includesToken(it.start, 'newline')) {
|
---|
581 | Object.assign(it, { key: null, sep: [this.sourceToken] });
|
---|
582 | }
|
---|
583 | else {
|
---|
584 | const start = getFirstKeyStartProps(it.start);
|
---|
585 | this.stack.push({
|
---|
586 | type: 'block-map',
|
---|
587 | offset: this.offset,
|
---|
588 | indent: this.indent,
|
---|
589 | items: [{ start, key: null, sep: [this.sourceToken] }]
|
---|
590 | });
|
---|
591 | }
|
---|
592 | }
|
---|
593 | else if (it.value) {
|
---|
594 | map.items.push({ start: [], key: null, sep: [this.sourceToken] });
|
---|
595 | }
|
---|
596 | else if (includesToken(it.sep, 'map-value-ind')) {
|
---|
597 | this.stack.push({
|
---|
598 | type: 'block-map',
|
---|
599 | offset: this.offset,
|
---|
600 | indent: this.indent,
|
---|
601 | items: [{ start, key: null, sep: [this.sourceToken] }]
|
---|
602 | });
|
---|
603 | }
|
---|
604 | else if (isFlowToken(it.key) &&
|
---|
605 | !includesToken(it.sep, 'newline')) {
|
---|
606 | const start = getFirstKeyStartProps(it.start);
|
---|
607 | const key = it.key;
|
---|
608 | const sep = it.sep;
|
---|
609 | sep.push(this.sourceToken);
|
---|
610 | // @ts-expect-error type guard is wrong here
|
---|
611 | delete it.key, delete it.sep;
|
---|
612 | this.stack.push({
|
---|
613 | type: 'block-map',
|
---|
614 | offset: this.offset,
|
---|
615 | indent: this.indent,
|
---|
616 | items: [{ start, key, sep }]
|
---|
617 | });
|
---|
618 | }
|
---|
619 | else if (start.length > 0) {
|
---|
620 | // Not actually at next item
|
---|
621 | it.sep = it.sep.concat(start, this.sourceToken);
|
---|
622 | }
|
---|
623 | else {
|
---|
624 | it.sep.push(this.sourceToken);
|
---|
625 | }
|
---|
626 | }
|
---|
627 | else {
|
---|
628 | if (!it.sep) {
|
---|
629 | Object.assign(it, { key: null, sep: [this.sourceToken] });
|
---|
630 | }
|
---|
631 | else if (it.value || atNextItem) {
|
---|
632 | map.items.push({ start, key: null, sep: [this.sourceToken] });
|
---|
633 | }
|
---|
634 | else if (includesToken(it.sep, 'map-value-ind')) {
|
---|
635 | this.stack.push({
|
---|
636 | type: 'block-map',
|
---|
637 | offset: this.offset,
|
---|
638 | indent: this.indent,
|
---|
639 | items: [{ start: [], key: null, sep: [this.sourceToken] }]
|
---|
640 | });
|
---|
641 | }
|
---|
642 | else {
|
---|
643 | it.sep.push(this.sourceToken);
|
---|
644 | }
|
---|
645 | }
|
---|
646 | this.onKeyLine = true;
|
---|
647 | return;
|
---|
648 | case 'alias':
|
---|
649 | case 'scalar':
|
---|
650 | case 'single-quoted-scalar':
|
---|
651 | case 'double-quoted-scalar': {
|
---|
652 | const fs = this.flowScalar(this.type);
|
---|
653 | if (atNextItem || it.value) {
|
---|
654 | map.items.push({ start, key: fs, sep: [] });
|
---|
655 | this.onKeyLine = true;
|
---|
656 | }
|
---|
657 | else if (it.sep) {
|
---|
658 | this.stack.push(fs);
|
---|
659 | }
|
---|
660 | else {
|
---|
661 | Object.assign(it, { key: fs, sep: [] });
|
---|
662 | this.onKeyLine = true;
|
---|
663 | }
|
---|
664 | return;
|
---|
665 | }
|
---|
666 | default: {
|
---|
667 | const bv = this.startBlockValue(map);
|
---|
668 | if (bv) {
|
---|
669 | if (atNextItem &&
|
---|
670 | bv.type !== 'block-seq' &&
|
---|
671 | includesToken(it.start, 'explicit-key-ind')) {
|
---|
672 | map.items.push({ start });
|
---|
673 | }
|
---|
674 | this.stack.push(bv);
|
---|
675 | return;
|
---|
676 | }
|
---|
677 | }
|
---|
678 | }
|
---|
679 | }
|
---|
680 | yield* this.pop();
|
---|
681 | yield* this.step();
|
---|
682 | }
|
---|
683 | *blockSequence(seq) {
|
---|
684 | const it = seq.items[seq.items.length - 1];
|
---|
685 | switch (this.type) {
|
---|
686 | case 'newline':
|
---|
687 | if (it.value) {
|
---|
688 | const end = 'end' in it.value ? it.value.end : undefined;
|
---|
689 | const last = Array.isArray(end) ? end[end.length - 1] : undefined;
|
---|
690 | if (last?.type === 'comment')
|
---|
691 | end?.push(this.sourceToken);
|
---|
692 | else
|
---|
693 | seq.items.push({ start: [this.sourceToken] });
|
---|
694 | }
|
---|
695 | else
|
---|
696 | it.start.push(this.sourceToken);
|
---|
697 | return;
|
---|
698 | case 'space':
|
---|
699 | case 'comment':
|
---|
700 | if (it.value)
|
---|
701 | seq.items.push({ start: [this.sourceToken] });
|
---|
702 | else {
|
---|
703 | if (this.atIndentedComment(it.start, seq.indent)) {
|
---|
704 | const prev = seq.items[seq.items.length - 2];
|
---|
705 | const end = prev?.value?.end;
|
---|
706 | if (Array.isArray(end)) {
|
---|
707 | Array.prototype.push.apply(end, it.start);
|
---|
708 | end.push(this.sourceToken);
|
---|
709 | seq.items.pop();
|
---|
710 | return;
|
---|
711 | }
|
---|
712 | }
|
---|
713 | it.start.push(this.sourceToken);
|
---|
714 | }
|
---|
715 | return;
|
---|
716 | case 'anchor':
|
---|
717 | case 'tag':
|
---|
718 | if (it.value || this.indent <= seq.indent)
|
---|
719 | break;
|
---|
720 | it.start.push(this.sourceToken);
|
---|
721 | return;
|
---|
722 | case 'seq-item-ind':
|
---|
723 | if (this.indent !== seq.indent)
|
---|
724 | break;
|
---|
725 | if (it.value || includesToken(it.start, 'seq-item-ind'))
|
---|
726 | seq.items.push({ start: [this.sourceToken] });
|
---|
727 | else
|
---|
728 | it.start.push(this.sourceToken);
|
---|
729 | return;
|
---|
730 | }
|
---|
731 | if (this.indent > seq.indent) {
|
---|
732 | const bv = this.startBlockValue(seq);
|
---|
733 | if (bv) {
|
---|
734 | this.stack.push(bv);
|
---|
735 | return;
|
---|
736 | }
|
---|
737 | }
|
---|
738 | yield* this.pop();
|
---|
739 | yield* this.step();
|
---|
740 | }
|
---|
741 | *flowCollection(fc) {
|
---|
742 | const it = fc.items[fc.items.length - 1];
|
---|
743 | if (this.type === 'flow-error-end') {
|
---|
744 | let top;
|
---|
745 | do {
|
---|
746 | yield* this.pop();
|
---|
747 | top = this.peek(1);
|
---|
748 | } while (top && top.type === 'flow-collection');
|
---|
749 | }
|
---|
750 | else if (fc.end.length === 0) {
|
---|
751 | switch (this.type) {
|
---|
752 | case 'comma':
|
---|
753 | case 'explicit-key-ind':
|
---|
754 | if (!it || it.sep)
|
---|
755 | fc.items.push({ start: [this.sourceToken] });
|
---|
756 | else
|
---|
757 | it.start.push(this.sourceToken);
|
---|
758 | return;
|
---|
759 | case 'map-value-ind':
|
---|
760 | if (!it || it.value)
|
---|
761 | fc.items.push({ start: [], key: null, sep: [this.sourceToken] });
|
---|
762 | else if (it.sep)
|
---|
763 | it.sep.push(this.sourceToken);
|
---|
764 | else
|
---|
765 | Object.assign(it, { key: null, sep: [this.sourceToken] });
|
---|
766 | return;
|
---|
767 | case 'space':
|
---|
768 | case 'comment':
|
---|
769 | case 'newline':
|
---|
770 | case 'anchor':
|
---|
771 | case 'tag':
|
---|
772 | if (!it || it.value)
|
---|
773 | fc.items.push({ start: [this.sourceToken] });
|
---|
774 | else if (it.sep)
|
---|
775 | it.sep.push(this.sourceToken);
|
---|
776 | else
|
---|
777 | it.start.push(this.sourceToken);
|
---|
778 | return;
|
---|
779 | case 'alias':
|
---|
780 | case 'scalar':
|
---|
781 | case 'single-quoted-scalar':
|
---|
782 | case 'double-quoted-scalar': {
|
---|
783 | const fs = this.flowScalar(this.type);
|
---|
784 | if (!it || it.value)
|
---|
785 | fc.items.push({ start: [], key: fs, sep: [] });
|
---|
786 | else if (it.sep)
|
---|
787 | this.stack.push(fs);
|
---|
788 | else
|
---|
789 | Object.assign(it, { key: fs, sep: [] });
|
---|
790 | return;
|
---|
791 | }
|
---|
792 | case 'flow-map-end':
|
---|
793 | case 'flow-seq-end':
|
---|
794 | fc.end.push(this.sourceToken);
|
---|
795 | return;
|
---|
796 | }
|
---|
797 | const bv = this.startBlockValue(fc);
|
---|
798 | /* istanbul ignore else should not happen */
|
---|
799 | if (bv)
|
---|
800 | this.stack.push(bv);
|
---|
801 | else {
|
---|
802 | yield* this.pop();
|
---|
803 | yield* this.step();
|
---|
804 | }
|
---|
805 | }
|
---|
806 | else {
|
---|
807 | const parent = this.peek(2);
|
---|
808 | if (parent.type === 'block-map' &&
|
---|
809 | ((this.type === 'map-value-ind' && parent.indent === fc.indent) ||
|
---|
810 | (this.type === 'newline' &&
|
---|
811 | !parent.items[parent.items.length - 1].sep))) {
|
---|
812 | yield* this.pop();
|
---|
813 | yield* this.step();
|
---|
814 | }
|
---|
815 | else if (this.type === 'map-value-ind' &&
|
---|
816 | parent.type !== 'flow-collection') {
|
---|
817 | const prev = getPrevProps(parent);
|
---|
818 | const start = getFirstKeyStartProps(prev);
|
---|
819 | fixFlowSeqItems(fc);
|
---|
820 | const sep = fc.end.splice(1, fc.end.length);
|
---|
821 | sep.push(this.sourceToken);
|
---|
822 | const map = {
|
---|
823 | type: 'block-map',
|
---|
824 | offset: fc.offset,
|
---|
825 | indent: fc.indent,
|
---|
826 | items: [{ start, key: fc, sep }]
|
---|
827 | };
|
---|
828 | this.onKeyLine = true;
|
---|
829 | this.stack[this.stack.length - 1] = map;
|
---|
830 | }
|
---|
831 | else {
|
---|
832 | yield* this.lineEnd(fc);
|
---|
833 | }
|
---|
834 | }
|
---|
835 | }
|
---|
836 | flowScalar(type) {
|
---|
837 | if (this.onNewLine) {
|
---|
838 | let nl = this.source.indexOf('\n') + 1;
|
---|
839 | while (nl !== 0) {
|
---|
840 | this.onNewLine(this.offset + nl);
|
---|
841 | nl = this.source.indexOf('\n', nl) + 1;
|
---|
842 | }
|
---|
843 | }
|
---|
844 | return {
|
---|
845 | type,
|
---|
846 | offset: this.offset,
|
---|
847 | indent: this.indent,
|
---|
848 | source: this.source
|
---|
849 | };
|
---|
850 | }
|
---|
851 | startBlockValue(parent) {
|
---|
852 | switch (this.type) {
|
---|
853 | case 'alias':
|
---|
854 | case 'scalar':
|
---|
855 | case 'single-quoted-scalar':
|
---|
856 | case 'double-quoted-scalar':
|
---|
857 | return this.flowScalar(this.type);
|
---|
858 | case 'block-scalar-header':
|
---|
859 | return {
|
---|
860 | type: 'block-scalar',
|
---|
861 | offset: this.offset,
|
---|
862 | indent: this.indent,
|
---|
863 | props: [this.sourceToken],
|
---|
864 | source: ''
|
---|
865 | };
|
---|
866 | case 'flow-map-start':
|
---|
867 | case 'flow-seq-start':
|
---|
868 | return {
|
---|
869 | type: 'flow-collection',
|
---|
870 | offset: this.offset,
|
---|
871 | indent: this.indent,
|
---|
872 | start: this.sourceToken,
|
---|
873 | items: [],
|
---|
874 | end: []
|
---|
875 | };
|
---|
876 | case 'seq-item-ind':
|
---|
877 | return {
|
---|
878 | type: 'block-seq',
|
---|
879 | offset: this.offset,
|
---|
880 | indent: this.indent,
|
---|
881 | items: [{ start: [this.sourceToken] }]
|
---|
882 | };
|
---|
883 | case 'explicit-key-ind': {
|
---|
884 | this.onKeyLine = true;
|
---|
885 | const prev = getPrevProps(parent);
|
---|
886 | const start = getFirstKeyStartProps(prev);
|
---|
887 | start.push(this.sourceToken);
|
---|
888 | return {
|
---|
889 | type: 'block-map',
|
---|
890 | offset: this.offset,
|
---|
891 | indent: this.indent,
|
---|
892 | items: [{ start }]
|
---|
893 | };
|
---|
894 | }
|
---|
895 | case 'map-value-ind': {
|
---|
896 | this.onKeyLine = true;
|
---|
897 | const prev = getPrevProps(parent);
|
---|
898 | const start = getFirstKeyStartProps(prev);
|
---|
899 | return {
|
---|
900 | type: 'block-map',
|
---|
901 | offset: this.offset,
|
---|
902 | indent: this.indent,
|
---|
903 | items: [{ start, key: null, sep: [this.sourceToken] }]
|
---|
904 | };
|
---|
905 | }
|
---|
906 | }
|
---|
907 | return null;
|
---|
908 | }
|
---|
909 | atIndentedComment(start, indent) {
|
---|
910 | if (this.type !== 'comment')
|
---|
911 | return false;
|
---|
912 | if (this.indent <= indent)
|
---|
913 | return false;
|
---|
914 | return start.every(st => st.type === 'newline' || st.type === 'space');
|
---|
915 | }
|
---|
916 | *documentEnd(docEnd) {
|
---|
917 | if (this.type !== 'doc-mode') {
|
---|
918 | if (docEnd.end)
|
---|
919 | docEnd.end.push(this.sourceToken);
|
---|
920 | else
|
---|
921 | docEnd.end = [this.sourceToken];
|
---|
922 | if (this.type === 'newline')
|
---|
923 | yield* this.pop();
|
---|
924 | }
|
---|
925 | }
|
---|
926 | *lineEnd(token) {
|
---|
927 | switch (this.type) {
|
---|
928 | case 'comma':
|
---|
929 | case 'doc-start':
|
---|
930 | case 'doc-end':
|
---|
931 | case 'flow-seq-end':
|
---|
932 | case 'flow-map-end':
|
---|
933 | case 'map-value-ind':
|
---|
934 | yield* this.pop();
|
---|
935 | yield* this.step();
|
---|
936 | break;
|
---|
937 | case 'newline':
|
---|
938 | this.onKeyLine = false;
|
---|
939 | // fallthrough
|
---|
940 | case 'space':
|
---|
941 | case 'comment':
|
---|
942 | default:
|
---|
943 | // all other values are errors
|
---|
944 | if (token.end)
|
---|
945 | token.end.push(this.sourceToken);
|
---|
946 | else
|
---|
947 | token.end = [this.sourceToken];
|
---|
948 | if (this.type === 'newline')
|
---|
949 | yield* this.pop();
|
---|
950 | }
|
---|
951 | }
|
---|
952 | }
|
---|
953 |
|
---|
954 | exports.Parser = Parser;
|
---|