state.js 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. import {reservedWords, keywords} from "./identifier"
  2. import {types as tt} from "./tokentype"
  3. import {lineBreak} from "./whitespace"
  4. import {getOptions} from "./options"
  5. // Registered plugins
  6. export const plugins = {}
  7. function keywordRegexp(words) {
  8. return new RegExp("^(" + words.replace(/ /g, "|") + ")$")
  9. }
  10. export class Parser {
  11. constructor(options, input, startPos) {
  12. this.options = options = getOptions(options)
  13. this.sourceFile = options.sourceFile
  14. this.keywords = keywordRegexp(keywords[options.ecmaVersion >= 6 ? 6 : 5])
  15. let reserved = options.allowReserved ? "" :
  16. reservedWords[options.ecmaVersion] + (options.sourceType == "module" ? " await" : "")
  17. this.reservedWords = keywordRegexp(reserved)
  18. let reservedStrict = (reserved ? reserved + " " : "") + reservedWords.strict
  19. this.reservedWordsStrict = keywordRegexp(reservedStrict)
  20. this.reservedWordsStrictBind = keywordRegexp(reservedStrict + " " + reservedWords.strictBind)
  21. this.input = String(input)
  22. // Used to signal to callers of `readWord1` whether the word
  23. // contained any escape sequences. This is needed because words with
  24. // escape sequences must not be interpreted as keywords.
  25. this.containsEsc = false;
  26. // Load plugins
  27. this.loadPlugins(options.plugins)
  28. // Set up token state
  29. // The current position of the tokenizer in the input.
  30. if (startPos) {
  31. this.pos = startPos
  32. this.lineStart = Math.max(0, this.input.lastIndexOf("\n", startPos))
  33. this.curLine = this.input.slice(0, this.lineStart).split(lineBreak).length
  34. } else {
  35. this.pos = this.lineStart = 0
  36. this.curLine = 1
  37. }
  38. // Properties of the current token:
  39. // Its type
  40. this.type = tt.eof
  41. // For tokens that include more information than their type, the value
  42. this.value = null
  43. // Its start and end offset
  44. this.start = this.end = this.pos
  45. // And, if locations are used, the {line, column} object
  46. // corresponding to those offsets
  47. this.startLoc = this.endLoc = this.curPosition()
  48. // Position information for the previous token
  49. this.lastTokEndLoc = this.lastTokStartLoc = null
  50. this.lastTokStart = this.lastTokEnd = this.pos
  51. // The context stack is used to superficially track syntactic
  52. // context to predict whether a regular expression is allowed in a
  53. // given position.
  54. this.context = this.initialContext()
  55. this.exprAllowed = true
  56. // Figure out if it's a module code.
  57. this.strict = this.inModule = options.sourceType === "module"
  58. // Used to signify the start of a potential arrow function
  59. this.potentialArrowAt = -1
  60. // Flags to track whether we are in a function, a generator.
  61. this.inFunction = this.inGenerator = false
  62. // Labels in scope.
  63. this.labels = []
  64. // If enabled, skip leading hashbang line.
  65. if (this.pos === 0 && options.allowHashBang && this.input.slice(0, 2) === '#!')
  66. this.skipLineComment(2)
  67. }
  68. // DEPRECATED Kept for backwards compatibility until 3.0 in case a plugin uses them
  69. isKeyword(word) { return this.keywords.test(word) }
  70. isReservedWord(word) { return this.reservedWords.test(word) }
  71. extend(name, f) {
  72. this[name] = f(this[name])
  73. }
  74. loadPlugins(pluginConfigs) {
  75. for (let name in pluginConfigs) {
  76. let plugin = plugins[name]
  77. if (!plugin) throw new Error("Plugin '" + name + "' not found")
  78. plugin(this, pluginConfigs[name])
  79. }
  80. }
  81. parse() {
  82. let node = this.options.program || this.startNode()
  83. this.nextToken()
  84. return this.parseTopLevel(node)
  85. }
  86. }