lexer.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717
  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 = new Set('0123456789ABCDEFabcdef');
  73. const tagChars = new Set("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-#;/?:@&=+$_.!~*'()");
  74. const flowIndicatorChars = new Set(',[]{}');
  75. const invalidAnchorChars = new Set(' ,[]{}\n\r\t');
  76. const isNotAnchorChar = (ch) => !ch || invalidAnchorChars.has(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. if (typeof source !== 'string')
  143. throw TypeError('source is not a string');
  144. this.buffer = this.buffer ? this.buffer + source : source;
  145. this.lineEndPos = null;
  146. }
  147. this.atEnd = !incomplete;
  148. let next = this.next ?? 'stream';
  149. while (next && (incomplete || this.hasChars(1)))
  150. next = yield* this.parseNext(next);
  151. }
  152. atLineEnd() {
  153. let i = this.pos;
  154. let ch = this.buffer[i];
  155. while (ch === ' ' || ch === '\t')
  156. ch = this.buffer[++i];
  157. if (!ch || ch === '#' || ch === '\n')
  158. return true;
  159. if (ch === '\r')
  160. return this.buffer[i + 1] === '\n';
  161. return false;
  162. }
  163. charAt(n) {
  164. return this.buffer[this.pos + n];
  165. }
  166. continueScalar(offset) {
  167. let ch = this.buffer[offset];
  168. if (this.indentNext > 0) {
  169. let indent = 0;
  170. while (ch === ' ')
  171. ch = this.buffer[++indent + offset];
  172. if (ch === '\r') {
  173. const next = this.buffer[indent + offset + 1];
  174. if (next === '\n' || (!next && !this.atEnd))
  175. return offset + indent + 1;
  176. }
  177. return ch === '\n' || indent >= this.indentNext || (!ch && !this.atEnd)
  178. ? offset + indent
  179. : -1;
  180. }
  181. if (ch === '-' || ch === '.') {
  182. const dt = this.buffer.substr(offset, 3);
  183. if ((dt === '---' || dt === '...') && isEmpty(this.buffer[offset + 3]))
  184. return -1;
  185. }
  186. return offset;
  187. }
  188. getLine() {
  189. let end = this.lineEndPos;
  190. if (typeof end !== 'number' || (end !== -1 && end < this.pos)) {
  191. end = this.buffer.indexOf('\n', this.pos);
  192. this.lineEndPos = end;
  193. }
  194. if (end === -1)
  195. return this.atEnd ? this.buffer.substring(this.pos) : null;
  196. if (this.buffer[end - 1] === '\r')
  197. end -= 1;
  198. return this.buffer.substring(this.pos, end);
  199. }
  200. hasChars(n) {
  201. return this.pos + n <= this.buffer.length;
  202. }
  203. setNext(state) {
  204. this.buffer = this.buffer.substring(this.pos);
  205. this.pos = 0;
  206. this.lineEndPos = null;
  207. this.next = state;
  208. return null;
  209. }
  210. peek(n) {
  211. return this.buffer.substr(this.pos, n);
  212. }
  213. *parseNext(next) {
  214. switch (next) {
  215. case 'stream':
  216. return yield* this.parseStream();
  217. case 'line-start':
  218. return yield* this.parseLineStart();
  219. case 'block-start':
  220. return yield* this.parseBlockStart();
  221. case 'doc':
  222. return yield* this.parseDocument();
  223. case 'flow':
  224. return yield* this.parseFlowCollection();
  225. case 'quoted-scalar':
  226. return yield* this.parseQuotedScalar();
  227. case 'block-scalar':
  228. return yield* this.parseBlockScalar();
  229. case 'plain-scalar':
  230. return yield* this.parsePlainScalar();
  231. }
  232. }
  233. *parseStream() {
  234. let line = this.getLine();
  235. if (line === null)
  236. return this.setNext('stream');
  237. if (line[0] === BOM) {
  238. yield* this.pushCount(1);
  239. line = line.substring(1);
  240. }
  241. if (line[0] === '%') {
  242. let dirEnd = line.length;
  243. let cs = line.indexOf('#');
  244. while (cs !== -1) {
  245. const ch = line[cs - 1];
  246. if (ch === ' ' || ch === '\t') {
  247. dirEnd = cs - 1;
  248. break;
  249. }
  250. else {
  251. cs = line.indexOf('#', cs + 1);
  252. }
  253. }
  254. while (true) {
  255. const ch = line[dirEnd - 1];
  256. if (ch === ' ' || ch === '\t')
  257. dirEnd -= 1;
  258. else
  259. break;
  260. }
  261. const n = (yield* this.pushCount(dirEnd)) + (yield* this.pushSpaces(true));
  262. yield* this.pushCount(line.length - n); // possible comment
  263. this.pushNewline();
  264. return 'stream';
  265. }
  266. if (this.atLineEnd()) {
  267. const sp = yield* this.pushSpaces(true);
  268. yield* this.pushCount(line.length - sp);
  269. yield* this.pushNewline();
  270. return 'stream';
  271. }
  272. yield DOCUMENT;
  273. return yield* this.parseLineStart();
  274. }
  275. *parseLineStart() {
  276. const ch = this.charAt(0);
  277. if (!ch && !this.atEnd)
  278. return this.setNext('line-start');
  279. if (ch === '-' || ch === '.') {
  280. if (!this.atEnd && !this.hasChars(4))
  281. return this.setNext('line-start');
  282. const s = this.peek(3);
  283. if ((s === '---' || s === '...') && isEmpty(this.charAt(3))) {
  284. yield* this.pushCount(3);
  285. this.indentValue = 0;
  286. this.indentNext = 0;
  287. return s === '---' ? 'doc' : 'stream';
  288. }
  289. }
  290. this.indentValue = yield* this.pushSpaces(false);
  291. if (this.indentNext > this.indentValue && !isEmpty(this.charAt(1)))
  292. this.indentNext = this.indentValue;
  293. return yield* this.parseBlockStart();
  294. }
  295. *parseBlockStart() {
  296. const [ch0, ch1] = this.peek(2);
  297. if (!ch1 && !this.atEnd)
  298. return this.setNext('block-start');
  299. if ((ch0 === '-' || ch0 === '?' || ch0 === ':') && isEmpty(ch1)) {
  300. const n = (yield* this.pushCount(1)) + (yield* this.pushSpaces(true));
  301. this.indentNext = this.indentValue + 1;
  302. this.indentValue += n;
  303. return yield* this.parseBlockStart();
  304. }
  305. return 'doc';
  306. }
  307. *parseDocument() {
  308. yield* this.pushSpaces(true);
  309. const line = this.getLine();
  310. if (line === null)
  311. return this.setNext('doc');
  312. let n = yield* this.pushIndicators();
  313. switch (line[n]) {
  314. case '#':
  315. yield* this.pushCount(line.length - n);
  316. // fallthrough
  317. case undefined:
  318. yield* this.pushNewline();
  319. return yield* this.parseLineStart();
  320. case '{':
  321. case '[':
  322. yield* this.pushCount(1);
  323. this.flowKey = false;
  324. this.flowLevel = 1;
  325. return 'flow';
  326. case '}':
  327. case ']':
  328. // this is an error
  329. yield* this.pushCount(1);
  330. return 'doc';
  331. case '*':
  332. yield* this.pushUntil(isNotAnchorChar);
  333. return 'doc';
  334. case '"':
  335. case "'":
  336. return yield* this.parseQuotedScalar();
  337. case '|':
  338. case '>':
  339. n += yield* this.parseBlockScalarHeader();
  340. n += yield* this.pushSpaces(true);
  341. yield* this.pushCount(line.length - n);
  342. yield* this.pushNewline();
  343. return yield* this.parseBlockScalar();
  344. default:
  345. return yield* this.parsePlainScalar();
  346. }
  347. }
  348. *parseFlowCollection() {
  349. let nl, sp;
  350. let indent = -1;
  351. do {
  352. nl = yield* this.pushNewline();
  353. if (nl > 0) {
  354. sp = yield* this.pushSpaces(false);
  355. this.indentValue = indent = sp;
  356. }
  357. else {
  358. sp = 0;
  359. }
  360. sp += yield* this.pushSpaces(true);
  361. } while (nl + sp > 0);
  362. const line = this.getLine();
  363. if (line === null)
  364. return this.setNext('flow');
  365. if ((indent !== -1 && indent < this.indentNext && line[0] !== '#') ||
  366. (indent === 0 &&
  367. (line.startsWith('---') || line.startsWith('...')) &&
  368. isEmpty(line[3]))) {
  369. // Allowing for the terminal ] or } at the same (rather than greater)
  370. // indent level as the initial [ or { is technically invalid, but
  371. // failing here would be surprising to users.
  372. const atFlowEndMarker = indent === this.indentNext - 1 &&
  373. this.flowLevel === 1 &&
  374. (line[0] === ']' || line[0] === '}');
  375. if (!atFlowEndMarker) {
  376. // this is an error
  377. this.flowLevel = 0;
  378. yield FLOW_END;
  379. return yield* this.parseLineStart();
  380. }
  381. }
  382. let n = 0;
  383. while (line[n] === ',') {
  384. n += yield* this.pushCount(1);
  385. n += yield* this.pushSpaces(true);
  386. this.flowKey = false;
  387. }
  388. n += yield* this.pushIndicators();
  389. switch (line[n]) {
  390. case undefined:
  391. return 'flow';
  392. case '#':
  393. yield* this.pushCount(line.length - n);
  394. return 'flow';
  395. case '{':
  396. case '[':
  397. yield* this.pushCount(1);
  398. this.flowKey = false;
  399. this.flowLevel += 1;
  400. return 'flow';
  401. case '}':
  402. case ']':
  403. yield* this.pushCount(1);
  404. this.flowKey = true;
  405. this.flowLevel -= 1;
  406. return this.flowLevel ? 'flow' : 'doc';
  407. case '*':
  408. yield* this.pushUntil(isNotAnchorChar);
  409. return 'flow';
  410. case '"':
  411. case "'":
  412. this.flowKey = true;
  413. return yield* this.parseQuotedScalar();
  414. case ':': {
  415. const next = this.charAt(1);
  416. if (this.flowKey || isEmpty(next) || next === ',') {
  417. this.flowKey = false;
  418. yield* this.pushCount(1);
  419. yield* this.pushSpaces(true);
  420. return 'flow';
  421. }
  422. }
  423. // fallthrough
  424. default:
  425. this.flowKey = false;
  426. return yield* this.parsePlainScalar();
  427. }
  428. }
  429. *parseQuotedScalar() {
  430. const quote = this.charAt(0);
  431. let end = this.buffer.indexOf(quote, this.pos + 1);
  432. if (quote === "'") {
  433. while (end !== -1 && this.buffer[end + 1] === "'")
  434. end = this.buffer.indexOf("'", end + 2);
  435. }
  436. else {
  437. // double-quote
  438. while (end !== -1) {
  439. let n = 0;
  440. while (this.buffer[end - 1 - n] === '\\')
  441. n += 1;
  442. if (n % 2 === 0)
  443. break;
  444. end = this.buffer.indexOf('"', end + 1);
  445. }
  446. }
  447. // Only looking for newlines within the quotes
  448. const qb = this.buffer.substring(0, end);
  449. let nl = qb.indexOf('\n', this.pos);
  450. if (nl !== -1) {
  451. while (nl !== -1) {
  452. const cs = this.continueScalar(nl + 1);
  453. if (cs === -1)
  454. break;
  455. nl = qb.indexOf('\n', cs);
  456. }
  457. if (nl !== -1) {
  458. // this is an error caused by an unexpected unindent
  459. end = nl - (qb[nl - 1] === '\r' ? 2 : 1);
  460. }
  461. }
  462. if (end === -1) {
  463. if (!this.atEnd)
  464. return this.setNext('quoted-scalar');
  465. end = this.buffer.length;
  466. }
  467. yield* this.pushToIndex(end + 1, false);
  468. return this.flowLevel ? 'flow' : 'doc';
  469. }
  470. *parseBlockScalarHeader() {
  471. this.blockScalarIndent = -1;
  472. this.blockScalarKeep = false;
  473. let i = this.pos;
  474. while (true) {
  475. const ch = this.buffer[++i];
  476. if (ch === '+')
  477. this.blockScalarKeep = true;
  478. else if (ch > '0' && ch <= '9')
  479. this.blockScalarIndent = Number(ch) - 1;
  480. else if (ch !== '-')
  481. break;
  482. }
  483. return yield* this.pushUntil(ch => isEmpty(ch) || ch === '#');
  484. }
  485. *parseBlockScalar() {
  486. let nl = this.pos - 1; // may be -1 if this.pos === 0
  487. let indent = 0;
  488. let ch;
  489. loop: for (let i = this.pos; (ch = this.buffer[i]); ++i) {
  490. switch (ch) {
  491. case ' ':
  492. indent += 1;
  493. break;
  494. case '\n':
  495. nl = i;
  496. indent = 0;
  497. break;
  498. case '\r': {
  499. const next = this.buffer[i + 1];
  500. if (!next && !this.atEnd)
  501. return this.setNext('block-scalar');
  502. if (next === '\n')
  503. break;
  504. } // fallthrough
  505. default:
  506. break loop;
  507. }
  508. }
  509. if (!ch && !this.atEnd)
  510. return this.setNext('block-scalar');
  511. if (indent >= this.indentNext) {
  512. if (this.blockScalarIndent === -1)
  513. this.indentNext = indent;
  514. else {
  515. this.indentNext =
  516. this.blockScalarIndent + (this.indentNext === 0 ? 1 : this.indentNext);
  517. }
  518. do {
  519. const cs = this.continueScalar(nl + 1);
  520. if (cs === -1)
  521. break;
  522. nl = this.buffer.indexOf('\n', cs);
  523. } while (nl !== -1);
  524. if (nl === -1) {
  525. if (!this.atEnd)
  526. return this.setNext('block-scalar');
  527. nl = this.buffer.length;
  528. }
  529. }
  530. // Trailing insufficiently indented tabs are invalid.
  531. // To catch that during parsing, we include them in the block scalar value.
  532. let i = nl + 1;
  533. ch = this.buffer[i];
  534. while (ch === ' ')
  535. ch = this.buffer[++i];
  536. if (ch === '\t') {
  537. while (ch === '\t' || ch === ' ' || ch === '\r' || ch === '\n')
  538. ch = this.buffer[++i];
  539. nl = i - 1;
  540. }
  541. else if (!this.blockScalarKeep) {
  542. do {
  543. let i = nl - 1;
  544. let ch = this.buffer[i];
  545. if (ch === '\r')
  546. ch = this.buffer[--i];
  547. const lastChar = i; // Drop the line if last char not more indented
  548. while (ch === ' ')
  549. ch = this.buffer[--i];
  550. if (ch === '\n' && i >= this.pos && i + 1 + indent > lastChar)
  551. nl = i;
  552. else
  553. break;
  554. } while (true);
  555. }
  556. yield SCALAR;
  557. yield* this.pushToIndex(nl + 1, true);
  558. return yield* this.parseLineStart();
  559. }
  560. *parsePlainScalar() {
  561. const inFlow = this.flowLevel > 0;
  562. let end = this.pos - 1;
  563. let i = this.pos - 1;
  564. let ch;
  565. while ((ch = this.buffer[++i])) {
  566. if (ch === ':') {
  567. const next = this.buffer[i + 1];
  568. if (isEmpty(next) || (inFlow && flowIndicatorChars.has(next)))
  569. break;
  570. end = i;
  571. }
  572. else if (isEmpty(ch)) {
  573. let next = this.buffer[i + 1];
  574. if (ch === '\r') {
  575. if (next === '\n') {
  576. i += 1;
  577. ch = '\n';
  578. next = this.buffer[i + 1];
  579. }
  580. else
  581. end = i;
  582. }
  583. if (next === '#' || (inFlow && flowIndicatorChars.has(next)))
  584. break;
  585. if (ch === '\n') {
  586. const cs = this.continueScalar(i + 1);
  587. if (cs === -1)
  588. break;
  589. i = Math.max(i, cs - 2); // to advance, but still account for ' #'
  590. }
  591. }
  592. else {
  593. if (inFlow && flowIndicatorChars.has(ch))
  594. break;
  595. end = i;
  596. }
  597. }
  598. if (!ch && !this.atEnd)
  599. return this.setNext('plain-scalar');
  600. yield SCALAR;
  601. yield* this.pushToIndex(end + 1, true);
  602. return inFlow ? 'flow' : 'doc';
  603. }
  604. *pushCount(n) {
  605. if (n > 0) {
  606. yield this.buffer.substr(this.pos, n);
  607. this.pos += n;
  608. return n;
  609. }
  610. return 0;
  611. }
  612. *pushToIndex(i, allowEmpty) {
  613. const s = this.buffer.slice(this.pos, i);
  614. if (s) {
  615. yield s;
  616. this.pos += s.length;
  617. return s.length;
  618. }
  619. else if (allowEmpty)
  620. yield '';
  621. return 0;
  622. }
  623. *pushIndicators() {
  624. switch (this.charAt(0)) {
  625. case '!':
  626. return ((yield* this.pushTag()) +
  627. (yield* this.pushSpaces(true)) +
  628. (yield* this.pushIndicators()));
  629. case '&':
  630. return ((yield* this.pushUntil(isNotAnchorChar)) +
  631. (yield* this.pushSpaces(true)) +
  632. (yield* this.pushIndicators()));
  633. case '-': // this is an error
  634. case '?': // this is an error outside flow collections
  635. case ':': {
  636. const inFlow = this.flowLevel > 0;
  637. const ch1 = this.charAt(1);
  638. if (isEmpty(ch1) || (inFlow && flowIndicatorChars.has(ch1))) {
  639. if (!inFlow)
  640. this.indentNext = this.indentValue + 1;
  641. else if (this.flowKey)
  642. this.flowKey = false;
  643. return ((yield* this.pushCount(1)) +
  644. (yield* this.pushSpaces(true)) +
  645. (yield* this.pushIndicators()));
  646. }
  647. }
  648. }
  649. return 0;
  650. }
  651. *pushTag() {
  652. if (this.charAt(1) === '<') {
  653. let i = this.pos + 2;
  654. let ch = this.buffer[i];
  655. while (!isEmpty(ch) && ch !== '>')
  656. ch = this.buffer[++i];
  657. return yield* this.pushToIndex(ch === '>' ? i + 1 : i, false);
  658. }
  659. else {
  660. let i = this.pos + 1;
  661. let ch = this.buffer[i];
  662. while (ch) {
  663. if (tagChars.has(ch))
  664. ch = this.buffer[++i];
  665. else if (ch === '%' &&
  666. hexDigits.has(this.buffer[i + 1]) &&
  667. hexDigits.has(this.buffer[i + 2])) {
  668. ch = this.buffer[(i += 3)];
  669. }
  670. else
  671. break;
  672. }
  673. return yield* this.pushToIndex(i, false);
  674. }
  675. }
  676. *pushNewline() {
  677. const ch = this.buffer[this.pos];
  678. if (ch === '\n')
  679. return yield* this.pushCount(1);
  680. else if (ch === '\r' && this.charAt(1) === '\n')
  681. return yield* this.pushCount(2);
  682. else
  683. return 0;
  684. }
  685. *pushSpaces(allowTabs) {
  686. let i = this.pos - 1;
  687. let ch;
  688. do {
  689. ch = this.buffer[++i];
  690. } while (ch === ' ' || (allowTabs && ch === '\t'));
  691. const n = i - this.pos;
  692. if (n > 0) {
  693. yield this.buffer.substr(this.pos, n);
  694. this.pos = i;
  695. }
  696. return n;
  697. }
  698. *pushUntil(test) {
  699. let i = this.pos;
  700. let ch = this.buffer[i];
  701. while (!test(ch))
  702. ch = this.buffer[++i];
  703. return yield* this.pushToIndex(i, false);
  704. }
  705. }
  706. export { Lexer };