[d24f17c] | 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;
|
---|