parser.js 34 KB

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