lexer.js 23 KB

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