lexer.js 24 KB

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