parser.js 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958
  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 = !it.explicitKey;
  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 atMapIndent = !this.onKeyLine && this.indent === map.indent;
  519. const atNextItem = atMapIndent &&
  520. (it.sep || it.explicitKey) &&
  521. this.type !== 'seq-item-ind';
  522. // For empty nodes, assign newline-separated not indented empty tokens to following node
  523. let start = [];
  524. if (atNextItem && it.sep && !it.value) {
  525. const nl = [];
  526. for (let i = 0; i < it.sep.length; ++i) {
  527. const st = it.sep[i];
  528. switch (st.type) {
  529. case 'newline':
  530. nl.push(i);
  531. break;
  532. case 'space':
  533. break;
  534. case 'comment':
  535. if (st.indent > map.indent)
  536. nl.length = 0;
  537. break;
  538. default:
  539. nl.length = 0;
  540. }
  541. }
  542. if (nl.length >= 2)
  543. start = it.sep.splice(nl[1]);
  544. }
  545. switch (this.type) {
  546. case 'anchor':
  547. case 'tag':
  548. if (atNextItem || it.value) {
  549. start.push(this.sourceToken);
  550. map.items.push({ start });
  551. this.onKeyLine = true;
  552. }
  553. else if (it.sep) {
  554. it.sep.push(this.sourceToken);
  555. }
  556. else {
  557. it.start.push(this.sourceToken);
  558. }
  559. return;
  560. case 'explicit-key-ind':
  561. if (!it.sep && !it.explicitKey) {
  562. it.start.push(this.sourceToken);
  563. it.explicitKey = true;
  564. }
  565. else if (atNextItem || it.value) {
  566. start.push(this.sourceToken);
  567. map.items.push({ start, explicitKey: true });
  568. }
  569. else {
  570. this.stack.push({
  571. type: 'block-map',
  572. offset: this.offset,
  573. indent: this.indent,
  574. items: [{ start: [this.sourceToken], explicitKey: true }]
  575. });
  576. }
  577. this.onKeyLine = true;
  578. return;
  579. case 'map-value-ind':
  580. if (it.explicitKey) {
  581. if (!it.sep) {
  582. if (includesToken(it.start, 'newline')) {
  583. Object.assign(it, { key: null, sep: [this.sourceToken] });
  584. }
  585. else {
  586. const start = getFirstKeyStartProps(it.start);
  587. this.stack.push({
  588. type: 'block-map',
  589. offset: this.offset,
  590. indent: this.indent,
  591. items: [{ start, key: null, sep: [this.sourceToken] }]
  592. });
  593. }
  594. }
  595. else if (it.value) {
  596. map.items.push({ start: [], key: null, sep: [this.sourceToken] });
  597. }
  598. else if (includesToken(it.sep, 'map-value-ind')) {
  599. this.stack.push({
  600. type: 'block-map',
  601. offset: this.offset,
  602. indent: this.indent,
  603. items: [{ start, key: null, sep: [this.sourceToken] }]
  604. });
  605. }
  606. else if (isFlowToken(it.key) &&
  607. !includesToken(it.sep, 'newline')) {
  608. const start = getFirstKeyStartProps(it.start);
  609. const key = it.key;
  610. const sep = it.sep;
  611. sep.push(this.sourceToken);
  612. // @ts-expect-error type guard is wrong here
  613. delete it.key;
  614. // @ts-expect-error type guard is wrong here
  615. delete it.sep;
  616. this.stack.push({
  617. type: 'block-map',
  618. offset: this.offset,
  619. indent: this.indent,
  620. items: [{ start, key, sep }]
  621. });
  622. }
  623. else if (start.length > 0) {
  624. // Not actually at next item
  625. it.sep = it.sep.concat(start, this.sourceToken);
  626. }
  627. else {
  628. it.sep.push(this.sourceToken);
  629. }
  630. }
  631. else {
  632. if (!it.sep) {
  633. Object.assign(it, { key: null, sep: [this.sourceToken] });
  634. }
  635. else if (it.value || atNextItem) {
  636. map.items.push({ start, key: null, sep: [this.sourceToken] });
  637. }
  638. else if (includesToken(it.sep, 'map-value-ind')) {
  639. this.stack.push({
  640. type: 'block-map',
  641. offset: this.offset,
  642. indent: this.indent,
  643. items: [{ start: [], key: null, sep: [this.sourceToken] }]
  644. });
  645. }
  646. else {
  647. it.sep.push(this.sourceToken);
  648. }
  649. }
  650. this.onKeyLine = true;
  651. return;
  652. case 'alias':
  653. case 'scalar':
  654. case 'single-quoted-scalar':
  655. case 'double-quoted-scalar': {
  656. const fs = this.flowScalar(this.type);
  657. if (atNextItem || it.value) {
  658. map.items.push({ start, key: fs, sep: [] });
  659. this.onKeyLine = true;
  660. }
  661. else if (it.sep) {
  662. this.stack.push(fs);
  663. }
  664. else {
  665. Object.assign(it, { key: fs, sep: [] });
  666. this.onKeyLine = true;
  667. }
  668. return;
  669. }
  670. default: {
  671. const bv = this.startBlockValue(map);
  672. if (bv) {
  673. if (atMapIndent && bv.type !== 'block-seq') {
  674. map.items.push({ start });
  675. }
  676. this.stack.push(bv);
  677. return;
  678. }
  679. }
  680. }
  681. }
  682. yield* this.pop();
  683. yield* this.step();
  684. }
  685. *blockSequence(seq) {
  686. const it = seq.items[seq.items.length - 1];
  687. switch (this.type) {
  688. case 'newline':
  689. if (it.value) {
  690. const end = 'end' in it.value ? it.value.end : undefined;
  691. const last = Array.isArray(end) ? end[end.length - 1] : undefined;
  692. if (last?.type === 'comment')
  693. end?.push(this.sourceToken);
  694. else
  695. seq.items.push({ start: [this.sourceToken] });
  696. }
  697. else
  698. it.start.push(this.sourceToken);
  699. return;
  700. case 'space':
  701. case 'comment':
  702. if (it.value)
  703. seq.items.push({ start: [this.sourceToken] });
  704. else {
  705. if (this.atIndentedComment(it.start, seq.indent)) {
  706. const prev = seq.items[seq.items.length - 2];
  707. const end = prev?.value?.end;
  708. if (Array.isArray(end)) {
  709. Array.prototype.push.apply(end, it.start);
  710. end.push(this.sourceToken);
  711. seq.items.pop();
  712. return;
  713. }
  714. }
  715. it.start.push(this.sourceToken);
  716. }
  717. return;
  718. case 'anchor':
  719. case 'tag':
  720. if (it.value || this.indent <= seq.indent)
  721. break;
  722. it.start.push(this.sourceToken);
  723. return;
  724. case 'seq-item-ind':
  725. if (this.indent !== seq.indent)
  726. break;
  727. if (it.value || includesToken(it.start, 'seq-item-ind'))
  728. seq.items.push({ start: [this.sourceToken] });
  729. else
  730. it.start.push(this.sourceToken);
  731. return;
  732. }
  733. if (this.indent > seq.indent) {
  734. const bv = this.startBlockValue(seq);
  735. if (bv) {
  736. this.stack.push(bv);
  737. return;
  738. }
  739. }
  740. yield* this.pop();
  741. yield* this.step();
  742. }
  743. *flowCollection(fc) {
  744. const it = fc.items[fc.items.length - 1];
  745. if (this.type === 'flow-error-end') {
  746. let top;
  747. do {
  748. yield* this.pop();
  749. top = this.peek(1);
  750. } while (top && top.type === 'flow-collection');
  751. }
  752. else if (fc.end.length === 0) {
  753. switch (this.type) {
  754. case 'comma':
  755. case 'explicit-key-ind':
  756. if (!it || it.sep)
  757. fc.items.push({ start: [this.sourceToken] });
  758. else
  759. it.start.push(this.sourceToken);
  760. return;
  761. case 'map-value-ind':
  762. if (!it || it.value)
  763. fc.items.push({ start: [], key: null, sep: [this.sourceToken] });
  764. else if (it.sep)
  765. it.sep.push(this.sourceToken);
  766. else
  767. Object.assign(it, { key: null, sep: [this.sourceToken] });
  768. return;
  769. case 'space':
  770. case 'comment':
  771. case 'newline':
  772. case 'anchor':
  773. case 'tag':
  774. if (!it || it.value)
  775. fc.items.push({ start: [this.sourceToken] });
  776. else if (it.sep)
  777. it.sep.push(this.sourceToken);
  778. else
  779. it.start.push(this.sourceToken);
  780. return;
  781. case 'alias':
  782. case 'scalar':
  783. case 'single-quoted-scalar':
  784. case 'double-quoted-scalar': {
  785. const fs = this.flowScalar(this.type);
  786. if (!it || it.value)
  787. fc.items.push({ start: [], key: fs, sep: [] });
  788. else if (it.sep)
  789. this.stack.push(fs);
  790. else
  791. Object.assign(it, { key: fs, sep: [] });
  792. return;
  793. }
  794. case 'flow-map-end':
  795. case 'flow-seq-end':
  796. fc.end.push(this.sourceToken);
  797. return;
  798. }
  799. const bv = this.startBlockValue(fc);
  800. /* istanbul ignore else should not happen */
  801. if (bv)
  802. this.stack.push(bv);
  803. else {
  804. yield* this.pop();
  805. yield* this.step();
  806. }
  807. }
  808. else {
  809. const parent = this.peek(2);
  810. if (parent.type === 'block-map' &&
  811. ((this.type === 'map-value-ind' && parent.indent === fc.indent) ||
  812. (this.type === 'newline' &&
  813. !parent.items[parent.items.length - 1].sep))) {
  814. yield* this.pop();
  815. yield* this.step();
  816. }
  817. else if (this.type === 'map-value-ind' &&
  818. parent.type !== 'flow-collection') {
  819. const prev = getPrevProps(parent);
  820. const start = getFirstKeyStartProps(prev);
  821. fixFlowSeqItems(fc);
  822. const sep = fc.end.splice(1, fc.end.length);
  823. sep.push(this.sourceToken);
  824. const map = {
  825. type: 'block-map',
  826. offset: fc.offset,
  827. indent: fc.indent,
  828. items: [{ start, key: fc, sep }]
  829. };
  830. this.onKeyLine = true;
  831. this.stack[this.stack.length - 1] = map;
  832. }
  833. else {
  834. yield* this.lineEnd(fc);
  835. }
  836. }
  837. }
  838. flowScalar(type) {
  839. if (this.onNewLine) {
  840. let nl = this.source.indexOf('\n') + 1;
  841. while (nl !== 0) {
  842. this.onNewLine(this.offset + nl);
  843. nl = this.source.indexOf('\n', nl) + 1;
  844. }
  845. }
  846. return {
  847. type,
  848. offset: this.offset,
  849. indent: this.indent,
  850. source: this.source
  851. };
  852. }
  853. startBlockValue(parent) {
  854. switch (this.type) {
  855. case 'alias':
  856. case 'scalar':
  857. case 'single-quoted-scalar':
  858. case 'double-quoted-scalar':
  859. return this.flowScalar(this.type);
  860. case 'block-scalar-header':
  861. return {
  862. type: 'block-scalar',
  863. offset: this.offset,
  864. indent: this.indent,
  865. props: [this.sourceToken],
  866. source: ''
  867. };
  868. case 'flow-map-start':
  869. case 'flow-seq-start':
  870. return {
  871. type: 'flow-collection',
  872. offset: this.offset,
  873. indent: this.indent,
  874. start: this.sourceToken,
  875. items: [],
  876. end: []
  877. };
  878. case 'seq-item-ind':
  879. return {
  880. type: 'block-seq',
  881. offset: this.offset,
  882. indent: this.indent,
  883. items: [{ start: [this.sourceToken] }]
  884. };
  885. case 'explicit-key-ind': {
  886. this.onKeyLine = true;
  887. const prev = getPrevProps(parent);
  888. const start = getFirstKeyStartProps(prev);
  889. start.push(this.sourceToken);
  890. return {
  891. type: 'block-map',
  892. offset: this.offset,
  893. indent: this.indent,
  894. items: [{ start, explicitKey: true }]
  895. };
  896. }
  897. case 'map-value-ind': {
  898. this.onKeyLine = true;
  899. const prev = getPrevProps(parent);
  900. const start = getFirstKeyStartProps(prev);
  901. return {
  902. type: 'block-map',
  903. offset: this.offset,
  904. indent: this.indent,
  905. items: [{ start, key: null, sep: [this.sourceToken] }]
  906. };
  907. }
  908. }
  909. return null;
  910. }
  911. atIndentedComment(start, indent) {
  912. if (this.type !== 'comment')
  913. return false;
  914. if (this.indent <= indent)
  915. return false;
  916. return start.every(st => st.type === 'newline' || st.type === 'space');
  917. }
  918. *documentEnd(docEnd) {
  919. if (this.type !== 'doc-mode') {
  920. if (docEnd.end)
  921. docEnd.end.push(this.sourceToken);
  922. else
  923. docEnd.end = [this.sourceToken];
  924. if (this.type === 'newline')
  925. yield* this.pop();
  926. }
  927. }
  928. *lineEnd(token) {
  929. switch (this.type) {
  930. case 'comma':
  931. case 'doc-start':
  932. case 'doc-end':
  933. case 'flow-seq-end':
  934. case 'flow-map-end':
  935. case 'map-value-ind':
  936. yield* this.pop();
  937. yield* this.step();
  938. break;
  939. case 'newline':
  940. this.onKeyLine = false;
  941. // fallthrough
  942. case 'space':
  943. case 'comment':
  944. default:
  945. // all other values are errors
  946. if (token.end)
  947. token.end.push(this.sourceToken);
  948. else
  949. token.end = [this.sourceToken];
  950. if (this.type === 'newline')
  951. yield* this.pop();
  952. }
  953. }
  954. }
  955. exports.Parser = Parser;