util.js 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. import {eat, finishToken, lookaheadTypeAndKeyword, match, nextTokenStart} from "../tokenizer/index";
  2. import {formatTokenType, TokenType as tt} from "../tokenizer/types";
  3. import {charCodes} from "../util/charcodes";
  4. import {input, state} from "./base";
  5. // ## Parser utilities
  6. // Tests whether parsed token is a contextual keyword.
  7. export function isContextual(contextualKeyword) {
  8. return state.contextualKeyword === contextualKeyword;
  9. }
  10. export function isLookaheadContextual(contextualKeyword) {
  11. const l = lookaheadTypeAndKeyword();
  12. return l.type === tt.name && l.contextualKeyword === contextualKeyword;
  13. }
  14. // Consumes contextual keyword if possible.
  15. export function eatContextual(contextualKeyword) {
  16. return state.contextualKeyword === contextualKeyword && eat(tt.name);
  17. }
  18. // Asserts that following token is given contextual keyword.
  19. export function expectContextual(contextualKeyword) {
  20. if (!eatContextual(contextualKeyword)) {
  21. unexpected();
  22. }
  23. }
  24. // Test whether a semicolon can be inserted at the current position.
  25. export function canInsertSemicolon() {
  26. return match(tt.eof) || match(tt.braceR) || hasPrecedingLineBreak();
  27. }
  28. export function hasPrecedingLineBreak() {
  29. const prevToken = state.tokens[state.tokens.length - 1];
  30. const lastTokEnd = prevToken ? prevToken.end : 0;
  31. for (let i = lastTokEnd; i < state.start; i++) {
  32. const code = input.charCodeAt(i);
  33. if (
  34. code === charCodes.lineFeed ||
  35. code === charCodes.carriageReturn ||
  36. code === 0x2028 ||
  37. code === 0x2029
  38. ) {
  39. return true;
  40. }
  41. }
  42. return false;
  43. }
  44. export function hasFollowingLineBreak() {
  45. const nextStart = nextTokenStart();
  46. for (let i = state.end; i < nextStart; i++) {
  47. const code = input.charCodeAt(i);
  48. if (
  49. code === charCodes.lineFeed ||
  50. code === charCodes.carriageReturn ||
  51. code === 0x2028 ||
  52. code === 0x2029
  53. ) {
  54. return true;
  55. }
  56. }
  57. return false;
  58. }
  59. export function isLineTerminator() {
  60. return eat(tt.semi) || canInsertSemicolon();
  61. }
  62. // Consume a semicolon, or, failing that, see if we are allowed to
  63. // pretend that there is a semicolon at this position.
  64. export function semicolon() {
  65. if (!isLineTerminator()) {
  66. unexpected('Unexpected token, expected ";"');
  67. }
  68. }
  69. // Expect a token of a given type. If found, consume it, otherwise,
  70. // raise an unexpected token error at given pos.
  71. export function expect(type) {
  72. const matched = eat(type);
  73. if (!matched) {
  74. unexpected(`Unexpected token, expected "${formatTokenType(type)}"`);
  75. }
  76. }
  77. /**
  78. * Transition the parser to an error state. All code needs to be written to naturally unwind in this
  79. * state, which allows us to backtrack without exceptions and without error plumbing everywhere.
  80. */
  81. export function unexpected(message = "Unexpected token", pos = state.start) {
  82. if (state.error) {
  83. return;
  84. }
  85. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  86. const err = new SyntaxError(message);
  87. err.pos = pos;
  88. state.error = err;
  89. state.pos = input.length;
  90. finishToken(tt.eof);
  91. }