lexer.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703
  1. 'use strict';
  2. var cst = require('./cst.js');
  3. /*
  4. START -> stream
  5. stream
  6. directive -> line-end -> stream
  7. indent + line-end -> stream
  8. [else] -> line-start
  9. line-end
  10. comment -> line-end
  11. newline -> .
  12. input-end -> END
  13. line-start
  14. doc-start -> doc
  15. doc-end -> stream
  16. [else] -> indent -> block-start
  17. block-start
  18. seq-item-start -> block-start
  19. explicit-key-start -> block-start
  20. map-value-start -> block-start
  21. [else] -> doc
  22. doc
  23. line-end -> line-start
  24. spaces -> doc
  25. anchor -> doc
  26. tag -> doc
  27. flow-start -> flow -> doc
  28. flow-end -> error -> doc
  29. seq-item-start -> error -> doc
  30. explicit-key-start -> error -> doc
  31. map-value-start -> doc
  32. alias -> doc
  33. quote-start -> quoted-scalar -> doc
  34. block-scalar-header -> line-end -> block-scalar(min) -> line-start
  35. [else] -> plain-scalar(false, min) -> doc
  36. flow
  37. line-end -> flow
  38. spaces -> flow
  39. anchor -> flow
  40. tag -> flow
  41. flow-start -> flow -> flow
  42. flow-end -> .
  43. seq-item-start -> error -> flow
  44. explicit-key-start -> flow
  45. map-value-start -> flow
  46. alias -> flow
  47. quote-start -> quoted-scalar -> flow
  48. comma -> flow
  49. [else] -> plain-scalar(true, 0) -> flow
  50. quoted-scalar
  51. quote-end -> .
  52. [else] -> quoted-scalar
  53. block-scalar(min)
  54. newline + peek(indent < min) -> .
  55. [else] -> block-scalar(min)
  56. plain-scalar(is-flow, min)
  57. scalar-end(is-flow) -> .
  58. peek(newline + (indent < min)) -> .
  59. [else] -> plain-scalar(min)
  60. */
  61. function isEmpty(ch) {
  62. switch (ch) {
  63. case undefined:
  64. case ' ':
  65. case '\n':
  66. case '\r':
  67. case '\t':
  68. return true;
  69. default:
  70. return false;
  71. }
  72. }
  73. const hexDigits = '0123456789ABCDEFabcdef'.split('');
  74. const tagChars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-#;/?:@&=+$_.!~*'()".split('');
  75. const invalidFlowScalarChars = ',[]{}'.split('');
  76. const invalidAnchorChars = ' ,[]{}\n\r\t'.split('');
  77. const isNotAnchorChar = (ch) => !ch || invalidAnchorChars.includes(ch);
  78. /**
  79. * Splits an input string into lexical tokens, i.e. smaller strings that are
  80. * easily identifiable by `tokens.tokenType()`.
  81. *
  82. * Lexing starts always in a "stream" context. Incomplete input may be buffered
  83. * until a complete token can be emitted.
  84. *
  85. * In addition to slices of the original input, the following control characters
  86. * may also be emitted:
  87. *
  88. * - `\x02` (Start of Text): A document starts with the next token
  89. * - `\x18` (Cancel): Unexpected end of flow-mode (indicates an error)
  90. * - `\x1f` (Unit Separator): Next token is a scalar value
  91. * - `\u{FEFF}` (Byte order mark): Emitted separately outside documents
  92. */
  93. class Lexer {
  94. constructor() {
  95. /**
  96. * Flag indicating whether the end of the current buffer marks the end of
  97. * all input
  98. */
  99. this.atEnd = false;
  100. /**
  101. * Explicit indent set in block scalar header, as an offset from the current
  102. * minimum indent, so e.g. set to 1 from a header `|2+`. Set to -1 if not
  103. * explicitly set.
  104. */
  105. this.blockScalarIndent = -1;
  106. /**
  107. * Block scalars that include a + (keep) chomping indicator in their header
  108. * include trailing empty lines, which are otherwise excluded from the
  109. * scalar's contents.
  110. */
  111. this.blockScalarKeep = false;
  112. /** Current input */
  113. this.buffer = '';
  114. /**
  115. * Flag noting whether the map value indicator : can immediately follow this
  116. * node within a flow context.
  117. */
  118. this.flowKey = false;
  119. /** Count of surrounding flow collection levels. */
  120. this.flowLevel = 0;
  121. /**
  122. * Minimum level of indentation required for next lines to be parsed as a
  123. * part of the current scalar value.
  124. */
  125. this.indentNext = 0;
  126. /** Indentation level of the current line. */
  127. this.indentValue = 0;
  128. /** Position of the next \n character. */
  129. this.lineEndPos = null;
  130. /** Stores the state of the lexer if reaching the end of incpomplete input */
  131. this.next = null;
  132. /** A pointer to `buffer`; the current position of the lexer. */
  133. this.pos = 0;
  134. }
  135. /**
  136. * Generate YAML tokens from the `source` string. If `incomplete`,
  137. * a part of the last line may be left as a buffer for the next call.
  138. *
  139. * @returns A generator of lexical tokens
  140. */
  141. *lex(source, incomplete = false) {
  142. if (source) {
  143. this.buffer = this.buffer ? this.buffer + source : source;
  144. this.lineEndPos = null;
  145. }
  146. this.atEnd = !incomplete;
  147. let next = this.next ?? 'stream';
  148. while (next && (incomplete || this.hasChars(1)))
  149. next = yield* this.parseNext(next);
  150. }
  151. atLineEnd() {
  152. let i = this.pos;
  153. let ch = this.buffer[i];
  154. while (ch === ' ' || ch === '\t')
  155. ch = this.buffer[++i];
  156. if (!ch || ch === '#' || ch === '\n')
  157. return true;
  158. if (ch === '\r')
  159. return this.buffer[i + 1] === '\n';
  160. return false;
  161. }
  162. charAt(n) {
  163. return this.buffer[this.pos + n];
  164. }
  165. continueScalar(offset) {
  166. let ch = this.buffer[offset];
  167. if (this.indentNext > 0) {
  168. let indent = 0;
  169. while (ch === ' ')
  170. ch = this.buffer[++indent + offset];
  171. if (ch === '\r') {
  172. const next = this.buffer[indent + offset + 1];
  173. if (next === '\n' || (!next && !this.atEnd))
  174. return offset + indent + 1;
  175. }
  176. return ch === '\n' || indent >= this.indentNext || (!ch && !this.atEnd)
  177. ? offset + indent
  178. : -1;
  179. }
  180. if (ch === '-' || ch === '.') {
  181. const dt = this.buffer.substr(offset, 3);
  182. if ((dt === '---' || dt === '...') && isEmpty(this.buffer[offset + 3]))
  183. return -1;
  184. }
  185. return offset;
  186. }
  187. getLine() {
  188. let end = this.lineEndPos;
  189. if (typeof end !== 'number' || (end !== -1 && end < this.pos)) {
  190. end = this.buffer.indexOf('\n', this.pos);
  191. this.lineEndPos = end;
  192. }
  193. if (end === -1)
  194. return this.atEnd ? this.buffer.substring(this.pos) : null;
  195. if (this.buffer[end - 1] === '\r')
  196. end -= 1;
  197. return this.buffer.substring(this.pos, end);
  198. }
  199. hasChars(n) {
  200. return this.pos + n <= this.buffer.length;
  201. }
  202. setNext(state) {
  203. this.buffer = this.buffer.substring(this.pos);
  204. this.pos = 0;
  205. this.lineEndPos = null;
  206. this.next = state;
  207. return null;
  208. }
  209. peek(n) {
  210. return this.buffer.substr(this.pos, n);
  211. }
  212. *parseNext(next) {
  213. switch (next) {
  214. case 'stream':
  215. return yield* this.parseStream();
  216. case 'line-start':
  217. return yield* this.parseLineStart();
  218. case 'block-start':
  219. return yield* this.parseBlockStart();
  220. case 'doc':
  221. return yield* this.parseDocument();
  222. case 'flow':
  223. return yield* this.parseFlowCollection();
  224. case 'quoted-scalar':
  225. return yield* this.parseQuotedScalar();
  226. case 'block-scalar':
  227. return yield* this.parseBlockScalar();
  228. case 'plain-scalar':
  229. return yield* this.parsePlainScalar();
  230. }
  231. }
  232. *parseStream() {
  233. let line = this.getLine();
  234. if (line === null)
  235. return this.setNext('stream');
  236. if (line[0] === cst.BOM) {
  237. yield* this.pushCount(1);
  238. line = line.substring(1);
  239. }
  240. if (line[0] === '%') {
  241. let dirEnd = line.length;
  242. const cs = line.indexOf('#');
  243. if (cs !== -1) {
  244. const ch = line[cs - 1];
  245. if (ch === ' ' || ch === '\t')
  246. dirEnd = cs - 1;
  247. }
  248. while (true) {
  249. const ch = line[dirEnd - 1];
  250. if (ch === ' ' || ch === '\t')
  251. dirEnd -= 1;
  252. else
  253. break;
  254. }
  255. const n = (yield* this.pushCount(dirEnd)) + (yield* this.pushSpaces(true));
  256. yield* this.pushCount(line.length - n); // possible comment
  257. this.pushNewline();
  258. return 'stream';
  259. }
  260. if (this.atLineEnd()) {
  261. const sp = yield* this.pushSpaces(true);
  262. yield* this.pushCount(line.length - sp);
  263. yield* this.pushNewline();
  264. return 'stream';
  265. }
  266. yield cst.DOCUMENT;
  267. return yield* this.parseLineStart();
  268. }
  269. *parseLineStart() {
  270. const ch = this.charAt(0);
  271. if (!ch && !this.atEnd)
  272. return this.setNext('line-start');
  273. if (ch === '-' || ch === '.') {
  274. if (!this.atEnd && !this.hasChars(4))
  275. return this.setNext('line-start');
  276. const s = this.peek(3);
  277. if (s === '---' && isEmpty(this.charAt(3))) {
  278. yield* this.pushCount(3);
  279. this.indentValue = 0;
  280. this.indentNext = 0;
  281. return 'doc';
  282. }
  283. else if (s === '...' && isEmpty(this.charAt(3))) {
  284. yield* this.pushCount(3);
  285. return 'stream';
  286. }
  287. }
  288. this.indentValue = yield* this.pushSpaces(false);
  289. if (this.indentNext > this.indentValue && !isEmpty(this.charAt(1)))
  290. this.indentNext = this.indentValue;
  291. return yield* this.parseBlockStart();
  292. }
  293. *parseBlockStart() {
  294. const [ch0, ch1] = this.peek(2);
  295. if (!ch1 && !this.atEnd)
  296. return this.setNext('block-start');
  297. if ((ch0 === '-' || ch0 === '?' || ch0 === ':') && isEmpty(ch1)) {
  298. const n = (yield* this.pushCount(1)) + (yield* this.pushSpaces(true));
  299. this.indentNext = this.indentValue + 1;
  300. this.indentValue += n;
  301. return yield* this.parseBlockStart();
  302. }
  303. return 'doc';
  304. }
  305. *parseDocument() {
  306. yield* this.pushSpaces(true);
  307. const line = this.getLine();
  308. if (line === null)
  309. return this.setNext('doc');
  310. let n = yield* this.pushIndicators();
  311. switch (line[n]) {
  312. case '#':
  313. yield* this.pushCount(line.length - n);
  314. // fallthrough
  315. case undefined:
  316. yield* this.pushNewline();
  317. return yield* this.parseLineStart();
  318. case '{':
  319. case '[':
  320. yield* this.pushCount(1);
  321. this.flowKey = false;
  322. this.flowLevel = 1;
  323. return 'flow';
  324. case '}':
  325. case ']':
  326. // this is an error
  327. yield* this.pushCount(1);
  328. return 'doc';
  329. case '*':
  330. yield* this.pushUntil(isNotAnchorChar);
  331. return 'doc';
  332. case '"':
  333. case "'":
  334. return yield* this.parseQuotedScalar();
  335. case '|':
  336. case '>':
  337. n += yield* this.parseBlockScalarHeader();
  338. n += yield* this.pushSpaces(true);
  339. yield* this.pushCount(line.length - n);
  340. yield* this.pushNewline();
  341. return yield* this.parseBlockScalar();
  342. default:
  343. return yield* this.parsePlainScalar();
  344. }
  345. }
  346. *parseFlowCollection() {
  347. let nl, sp;
  348. let indent = -1;
  349. do {
  350. nl = yield* this.pushNewline();
  351. if (nl > 0) {
  352. sp = yield* this.pushSpaces(false);
  353. this.indentValue = indent = sp;
  354. }
  355. else {
  356. sp = 0;
  357. }
  358. sp += yield* this.pushSpaces(true);
  359. } while (nl + sp > 0);
  360. const line = this.getLine();
  361. if (line === null)
  362. return this.setNext('flow');
  363. if ((indent !== -1 && indent < this.indentNext && line[0] !== '#') ||
  364. (indent === 0 &&
  365. (line.startsWith('---') || line.startsWith('...')) &&
  366. isEmpty(line[3]))) {
  367. // Allowing for the terminal ] or } at the same (rather than greater)
  368. // indent level as the initial [ or { is technically invalid, but
  369. // failing here would be surprising to users.
  370. const atFlowEndMarker = indent === this.indentNext - 1 &&
  371. this.flowLevel === 1 &&
  372. (line[0] === ']' || line[0] === '}');
  373. if (!atFlowEndMarker) {
  374. // this is an error
  375. this.flowLevel = 0;
  376. yield cst.FLOW_END;
  377. return yield* this.parseLineStart();
  378. }
  379. }
  380. let n = 0;
  381. while (line[n] === ',') {
  382. n += yield* this.pushCount(1);
  383. n += yield* this.pushSpaces(true);
  384. this.flowKey = false;
  385. }
  386. n += yield* this.pushIndicators();
  387. switch (line[n]) {
  388. case undefined:
  389. return 'flow';
  390. case '#':
  391. yield* this.pushCount(line.length - n);
  392. return 'flow';
  393. case '{':
  394. case '[':
  395. yield* this.pushCount(1);
  396. this.flowKey = false;
  397. this.flowLevel += 1;
  398. return 'flow';
  399. case '}':
  400. case ']':
  401. yield* this.pushCount(1);
  402. this.flowKey = true;
  403. this.flowLevel -= 1;
  404. return this.flowLevel ? 'flow' : 'doc';
  405. case '*':
  406. yield* this.pushUntil(isNotAnchorChar);
  407. return 'flow';
  408. case '"':
  409. case "'":
  410. this.flowKey = true;
  411. return yield* this.parseQuotedScalar();
  412. case ':': {
  413. const next = this.charAt(1);
  414. if (this.flowKey || isEmpty(next) || next === ',') {
  415. this.flowKey = false;
  416. yield* this.pushCount(1);
  417. yield* this.pushSpaces(true);
  418. return 'flow';
  419. }
  420. }
  421. // fallthrough
  422. default:
  423. this.flowKey = false;
  424. return yield* this.parsePlainScalar();
  425. }
  426. }
  427. *parseQuotedScalar() {
  428. const quote = this.charAt(0);
  429. let end = this.buffer.indexOf(quote, this.pos + 1);
  430. if (quote === "'") {
  431. while (end !== -1 && this.buffer[end + 1] === "'")
  432. end = this.buffer.indexOf("'", end + 2);
  433. }
  434. else {
  435. // double-quote
  436. while (end !== -1) {
  437. let n = 0;
  438. while (this.buffer[end - 1 - n] === '\\')
  439. n += 1;
  440. if (n % 2 === 0)
  441. break;
  442. end = this.buffer.indexOf('"', end + 1);
  443. }
  444. }
  445. // Only looking for newlines within the quotes
  446. const qb = this.buffer.substring(0, end);
  447. let nl = qb.indexOf('\n', this.pos);
  448. if (nl !== -1) {
  449. while (nl !== -1) {
  450. const cs = this.continueScalar(nl + 1);
  451. if (cs === -1)
  452. break;
  453. nl = qb.indexOf('\n', cs);
  454. }
  455. if (nl !== -1) {
  456. // this is an error caused by an unexpected unindent
  457. end = nl - (qb[nl - 1] === '\r' ? 2 : 1);
  458. }
  459. }
  460. if (end === -1) {
  461. if (!this.atEnd)
  462. return this.setNext('quoted-scalar');
  463. end = this.buffer.length;
  464. }
  465. yield* this.pushToIndex(end + 1, false);
  466. return this.flowLevel ? 'flow' : 'doc';
  467. }
  468. *parseBlockScalarHeader() {
  469. this.blockScalarIndent = -1;
  470. this.blockScalarKeep = false;
  471. let i = this.pos;
  472. while (true) {
  473. const ch = this.buffer[++i];
  474. if (ch === '+')
  475. this.blockScalarKeep = true;
  476. else if (ch > '0' && ch <= '9')
  477. this.blockScalarIndent = Number(ch) - 1;
  478. else if (ch !== '-')
  479. break;
  480. }
  481. return yield* this.pushUntil(ch => isEmpty(ch) || ch === '#');
  482. }
  483. *parseBlockScalar() {
  484. let nl = this.pos - 1; // may be -1 if this.pos === 0
  485. let indent = 0;
  486. let ch;
  487. loop: for (let i = this.pos; (ch = this.buffer[i]); ++i) {
  488. switch (ch) {
  489. case ' ':
  490. indent += 1;
  491. break;
  492. case '\n':
  493. nl = i;
  494. indent = 0;
  495. break;
  496. case '\r': {
  497. const next = this.buffer[i + 1];
  498. if (!next && !this.atEnd)
  499. return this.setNext('block-scalar');
  500. if (next === '\n')
  501. break;
  502. } // fallthrough
  503. default:
  504. break loop;
  505. }
  506. }
  507. if (!ch && !this.atEnd)
  508. return this.setNext('block-scalar');
  509. if (indent >= this.indentNext) {
  510. if (this.blockScalarIndent === -1)
  511. this.indentNext = indent;
  512. else
  513. this.indentNext += this.blockScalarIndent;
  514. do {
  515. const cs = this.continueScalar(nl + 1);
  516. if (cs === -1)
  517. break;
  518. nl = this.buffer.indexOf('\n', cs);
  519. } while (nl !== -1);
  520. if (nl === -1) {
  521. if (!this.atEnd)
  522. return this.setNext('block-scalar');
  523. nl = this.buffer.length;
  524. }
  525. }
  526. if (!this.blockScalarKeep) {
  527. do {
  528. let i = nl - 1;
  529. let ch = this.buffer[i];
  530. if (ch === '\r')
  531. ch = this.buffer[--i];
  532. const lastChar = i; // Drop the line if last char not more indented
  533. while (ch === ' ' || ch === '\t')
  534. ch = this.buffer[--i];
  535. if (ch === '\n' && i >= this.pos && i + 1 + indent > lastChar)
  536. nl = i;
  537. else
  538. break;
  539. } while (true);
  540. }
  541. yield cst.SCALAR;
  542. yield* this.pushToIndex(nl + 1, true);
  543. return yield* this.parseLineStart();
  544. }
  545. *parsePlainScalar() {
  546. const inFlow = this.flowLevel > 0;
  547. let end = this.pos - 1;
  548. let i = this.pos - 1;
  549. let ch;
  550. while ((ch = this.buffer[++i])) {
  551. if (ch === ':') {
  552. const next = this.buffer[i + 1];
  553. if (isEmpty(next) || (inFlow && next === ','))
  554. break;
  555. end = i;
  556. }
  557. else if (isEmpty(ch)) {
  558. let next = this.buffer[i + 1];
  559. if (ch === '\r') {
  560. if (next === '\n') {
  561. i += 1;
  562. ch = '\n';
  563. next = this.buffer[i + 1];
  564. }
  565. else
  566. end = i;
  567. }
  568. if (next === '#' || (inFlow && invalidFlowScalarChars.includes(next)))
  569. break;
  570. if (ch === '\n') {
  571. const cs = this.continueScalar(i + 1);
  572. if (cs === -1)
  573. break;
  574. i = Math.max(i, cs - 2); // to advance, but still account for ' #'
  575. }
  576. }
  577. else {
  578. if (inFlow && invalidFlowScalarChars.includes(ch))
  579. break;
  580. end = i;
  581. }
  582. }
  583. if (!ch && !this.atEnd)
  584. return this.setNext('plain-scalar');
  585. yield cst.SCALAR;
  586. yield* this.pushToIndex(end + 1, true);
  587. return inFlow ? 'flow' : 'doc';
  588. }
  589. *pushCount(n) {
  590. if (n > 0) {
  591. yield this.buffer.substr(this.pos, n);
  592. this.pos += n;
  593. return n;
  594. }
  595. return 0;
  596. }
  597. *pushToIndex(i, allowEmpty) {
  598. const s = this.buffer.slice(this.pos, i);
  599. if (s) {
  600. yield s;
  601. this.pos += s.length;
  602. return s.length;
  603. }
  604. else if (allowEmpty)
  605. yield '';
  606. return 0;
  607. }
  608. *pushIndicators() {
  609. switch (this.charAt(0)) {
  610. case '!':
  611. return ((yield* this.pushTag()) +
  612. (yield* this.pushSpaces(true)) +
  613. (yield* this.pushIndicators()));
  614. case '&':
  615. return ((yield* this.pushUntil(isNotAnchorChar)) +
  616. (yield* this.pushSpaces(true)) +
  617. (yield* this.pushIndicators()));
  618. case '-': // this is an error
  619. case '?': // this is an error outside flow collections
  620. case ':': {
  621. const inFlow = this.flowLevel > 0;
  622. const ch1 = this.charAt(1);
  623. if (isEmpty(ch1) || (inFlow && invalidFlowScalarChars.includes(ch1))) {
  624. if (!inFlow)
  625. this.indentNext = this.indentValue + 1;
  626. else if (this.flowKey)
  627. this.flowKey = false;
  628. return ((yield* this.pushCount(1)) +
  629. (yield* this.pushSpaces(true)) +
  630. (yield* this.pushIndicators()));
  631. }
  632. }
  633. }
  634. return 0;
  635. }
  636. *pushTag() {
  637. if (this.charAt(1) === '<') {
  638. let i = this.pos + 2;
  639. let ch = this.buffer[i];
  640. while (!isEmpty(ch) && ch !== '>')
  641. ch = this.buffer[++i];
  642. return yield* this.pushToIndex(ch === '>' ? i + 1 : i, false);
  643. }
  644. else {
  645. let i = this.pos + 1;
  646. let ch = this.buffer[i];
  647. while (ch) {
  648. if (tagChars.includes(ch))
  649. ch = this.buffer[++i];
  650. else if (ch === '%' &&
  651. hexDigits.includes(this.buffer[i + 1]) &&
  652. hexDigits.includes(this.buffer[i + 2])) {
  653. ch = this.buffer[(i += 3)];
  654. }
  655. else
  656. break;
  657. }
  658. return yield* this.pushToIndex(i, false);
  659. }
  660. }
  661. *pushNewline() {
  662. const ch = this.buffer[this.pos];
  663. if (ch === '\n')
  664. return yield* this.pushCount(1);
  665. else if (ch === '\r' && this.charAt(1) === '\n')
  666. return yield* this.pushCount(2);
  667. else
  668. return 0;
  669. }
  670. *pushSpaces(allowTabs) {
  671. let i = this.pos - 1;
  672. let ch;
  673. do {
  674. ch = this.buffer[++i];
  675. } while (ch === ' ' || (allowTabs && ch === '\t'));
  676. const n = i - this.pos;
  677. if (n > 0) {
  678. yield this.buffer.substr(this.pos, n);
  679. this.pos = i;
  680. }
  681. return n;
  682. }
  683. *pushUntil(test) {
  684. let i = this.pos;
  685. let ch = this.buffer[i];
  686. while (!test(ch))
  687. ch = this.buffer[++i];
  688. return yield* this.pushToIndex(i, false);
  689. }
  690. }
  691. exports.Lexer = Lexer;