1 import {isIdentifierStart, isIdentifierChar} from "./identifier.js"
2 import {types as tt, keywords as keywordTypes} from "./tokentype.js"
3 import {Parser} from "./state.js"
4 import {SourceLocation} from "./locutil.js"
5 import {RegExpValidationState} from "./regexp.js"
6 import {lineBreak, nextLineBreak, isNewLine, nonASCIIwhitespace} from "./whitespace.js"
8 // Object type used to represent tokens. Note that normally, tokens
9 // simply exist as properties on the parser object. This is only
10 // used for the onToken callback and the external tokenizer.
18 if (p.options.locations)
19 this.loc = new SourceLocation(p, p.startLoc, p.endLoc)
21 this.range = [p.start, p.end]
27 const pp = Parser.prototype
29 // Move to the next token
31 pp.next = function(ignoreEscapeSequenceInKeyword) {
32 if (!ignoreEscapeSequenceInKeyword && this.type.keyword && this.containsEsc)
33 this.raiseRecoverable(this.start, "Escape sequence in keyword " + this.type.keyword)
34 if (this.options.onToken)
35 this.options.onToken(new Token(this))
37 this.lastTokEnd = this.end
38 this.lastTokStart = this.start
39 this.lastTokEndLoc = this.endLoc
40 this.lastTokStartLoc = this.startLoc
44 pp.getToken = function() {
46 return new Token(this)
49 // If we're in an ES6 environment, make parsers iterable
50 if (typeof Symbol !== "undefined")
51 pp[Symbol.iterator] = function() {
54 let token = this.getToken()
56 done: token.type === tt.eof,
63 // Toggle strict mode. Re-reads the next number or string to please
64 // pedantic tests (`"use strict"; 010;` should fail).
66 // Read a single token, updating the parser object's token-related
69 pp.nextToken = function() {
70 let curContext = this.curContext()
71 if (!curContext || !curContext.preserveSpace) this.skipSpace()
74 if (this.options.locations) this.startLoc = this.curPosition()
75 if (this.pos >= this.input.length) return this.finishToken(tt.eof)
77 if (curContext.override) return curContext.override(this)
78 else this.readToken(this.fullCharCodeAtPos())
81 pp.readToken = function(code) {
82 // Identifier or keyword. '\uXXXX' sequences are allowed in
83 // identifiers, so '\' also dispatches to that.
84 if (isIdentifierStart(code, this.options.ecmaVersion >= 6) || code === 92 /* '\' */)
85 return this.readWord()
87 return this.getTokenFromCode(code)
90 pp.fullCharCodeAtPos = function() {
91 let code = this.input.charCodeAt(this.pos)
92 if (code <= 0xd7ff || code >= 0xdc00) return code
93 let next = this.input.charCodeAt(this.pos + 1)
94 return next <= 0xdbff || next >= 0xe000 ? code : (code << 10) + next - 0x35fdc00
97 pp.skipBlockComment = function() {
98 let startLoc = this.options.onComment && this.curPosition()
99 let start = this.pos, end = this.input.indexOf("*/", this.pos += 2)
100 if (end === -1) this.raise(this.pos - 2, "Unterminated comment")
102 if (this.options.locations) {
103 for (let nextBreak, pos = start; (nextBreak = nextLineBreak(this.input, pos, this.pos)) > -1;) {
105 pos = this.lineStart = nextBreak
108 if (this.options.onComment)
109 this.options.onComment(true, this.input.slice(start + 2, end), start, this.pos,
110 startLoc, this.curPosition())
113 pp.skipLineComment = function(startSkip) {
115 let startLoc = this.options.onComment && this.curPosition()
116 let ch = this.input.charCodeAt(this.pos += startSkip)
117 while (this.pos < this.input.length && !isNewLine(ch)) {
118 ch = this.input.charCodeAt(++this.pos)
120 if (this.options.onComment)
121 this.options.onComment(false, this.input.slice(start + startSkip, this.pos), start, this.pos,
122 startLoc, this.curPosition())
125 // Called at the start of the parse and after every token. Skips
126 // whitespace and comments, and.
128 pp.skipSpace = function() {
129 loop: while (this.pos < this.input.length) {
130 let ch = this.input.charCodeAt(this.pos)
132 case 32: case 160: // ' '
136 if (this.input.charCodeAt(this.pos + 1) === 10) {
139 case 10: case 8232: case 8233:
141 if (this.options.locations) {
143 this.lineStart = this.pos
147 switch (this.input.charCodeAt(this.pos + 1)) {
149 this.skipBlockComment()
152 this.skipLineComment(2)
159 if (ch > 8 && ch < 14 || ch >= 5760 && nonASCIIwhitespace.test(String.fromCharCode(ch))) {
168 // Called at the end of every token. Sets `end`, `val`, and
169 // maintains `context` and `exprAllowed`, and skips the space after
170 // the token, so that the next one's `start` will point at the
173 pp.finishToken = function(type, val) {
175 if (this.options.locations) this.endLoc = this.curPosition()
176 let prevType = this.type
180 this.updateContext(prevType)
185 // This is the function that is called to fetch the next token. It
186 // is somewhat obscure, because it works in character codes rather
187 // than characters, and because operator parsing has been inlined
190 // All in the name of speed.
192 pp.readToken_dot = function() {
193 let next = this.input.charCodeAt(this.pos + 1)
194 if (next >= 48 && next <= 57) return this.readNumber(true)
195 let next2 = this.input.charCodeAt(this.pos + 2)
196 if (this.options.ecmaVersion >= 6 && next === 46 && next2 === 46) { // 46 = dot '.'
198 return this.finishToken(tt.ellipsis)
201 return this.finishToken(tt.dot)
205 pp.readToken_slash = function() { // '/'
206 let next = this.input.charCodeAt(this.pos + 1)
207 if (this.exprAllowed) { ++this.pos; return this.readRegexp() }
208 if (next === 61) return this.finishOp(tt.assign, 2)
209 return this.finishOp(tt.slash, 1)
212 pp.readToken_mult_modulo_exp = function(code) { // '%*'
213 let next = this.input.charCodeAt(this.pos + 1)
215 let tokentype = code === 42 ? tt.star : tt.modulo
217 // exponentiation operator ** and **=
218 if (this.options.ecmaVersion >= 7 && code === 42 && next === 42) {
220 tokentype = tt.starstar
221 next = this.input.charCodeAt(this.pos + 2)
224 if (next === 61) return this.finishOp(tt.assign, size + 1)
225 return this.finishOp(tokentype, size)
228 pp.readToken_pipe_amp = function(code) { // '|&'
229 let next = this.input.charCodeAt(this.pos + 1)
231 if (this.options.ecmaVersion >= 12) {
232 let next2 = this.input.charCodeAt(this.pos + 2)
233 if (next2 === 61) return this.finishOp(tt.assign, 3)
235 return this.finishOp(code === 124 ? tt.logicalOR : tt.logicalAND, 2)
237 if (next === 61) return this.finishOp(tt.assign, 2)
238 return this.finishOp(code === 124 ? tt.bitwiseOR : tt.bitwiseAND, 1)
241 pp.readToken_caret = function() { // '^'
242 let next = this.input.charCodeAt(this.pos + 1)
243 if (next === 61) return this.finishOp(tt.assign, 2)
244 return this.finishOp(tt.bitwiseXOR, 1)
247 pp.readToken_plus_min = function(code) { // '+-'
248 let next = this.input.charCodeAt(this.pos + 1)
250 if (next === 45 && !this.inModule && this.input.charCodeAt(this.pos + 2) === 62 &&
251 (this.lastTokEnd === 0 || lineBreak.test(this.input.slice(this.lastTokEnd, this.pos)))) {
252 // A `-->` line comment
253 this.skipLineComment(3)
255 return this.nextToken()
257 return this.finishOp(tt.incDec, 2)
259 if (next === 61) return this.finishOp(tt.assign, 2)
260 return this.finishOp(tt.plusMin, 1)
263 pp.readToken_lt_gt = function(code) { // '<>'
264 let next = this.input.charCodeAt(this.pos + 1)
267 size = code === 62 && this.input.charCodeAt(this.pos + 2) === 62 ? 3 : 2
268 if (this.input.charCodeAt(this.pos + size) === 61) return this.finishOp(tt.assign, size + 1)
269 return this.finishOp(tt.bitShift, size)
271 if (next === 33 && code === 60 && !this.inModule && this.input.charCodeAt(this.pos + 2) === 45 &&
272 this.input.charCodeAt(this.pos + 3) === 45) {
273 // `<!--`, an XML-style comment that should be interpreted as a line comment
274 this.skipLineComment(4)
276 return this.nextToken()
278 if (next === 61) size = 2
279 return this.finishOp(tt.relational, size)
282 pp.readToken_eq_excl = function(code) { // '=!'
283 let next = this.input.charCodeAt(this.pos + 1)
284 if (next === 61) return this.finishOp(tt.equality, this.input.charCodeAt(this.pos + 2) === 61 ? 3 : 2)
285 if (code === 61 && next === 62 && this.options.ecmaVersion >= 6) { // '=>'
287 return this.finishToken(tt.arrow)
289 return this.finishOp(code === 61 ? tt.eq : tt.prefix, 1)
292 pp.readToken_question = function() { // '?'
293 const ecmaVersion = this.options.ecmaVersion
294 if (ecmaVersion >= 11) {
295 let next = this.input.charCodeAt(this.pos + 1)
297 let next2 = this.input.charCodeAt(this.pos + 2)
298 if (next2 < 48 || next2 > 57) return this.finishOp(tt.questionDot, 2)
301 if (ecmaVersion >= 12) {
302 let next2 = this.input.charCodeAt(this.pos + 2)
303 if (next2 === 61) return this.finishOp(tt.assign, 3)
305 return this.finishOp(tt.coalesce, 2)
308 return this.finishOp(tt.question, 1)
311 pp.readToken_numberSign = function() { // '#'
312 const ecmaVersion = this.options.ecmaVersion
314 if (ecmaVersion >= 13) {
316 code = this.fullCharCodeAtPos()
317 if (isIdentifierStart(code, true) || code === 92 /* '\' */) {
318 return this.finishToken(tt.privateId, this.readWord1())
322 this.raise(this.pos, "Unexpected character '" + codePointToString(code) + "'")
325 pp.getTokenFromCode = function(code) {
327 // The interpretation of a dot depends on whether it is followed
328 // by a digit or another two dots.
330 return this.readToken_dot()
332 ++this.pos; return this.finishToken(tt.hash)
334 // Punctuation tokens.
335 case 40: ++this.pos; return this.finishToken(tt.parenL)
336 case 41: ++this.pos; return this.finishToken(tt.parenR)
337 case 59: ++this.pos; return this.finishToken(tt.semi)
338 case 44: ++this.pos; return this.finishToken(tt.comma)
339 case 91: ++this.pos; return this.finishToken(tt.bracketL)
340 case 93: ++this.pos; return this.finishToken(tt.bracketR)
341 case 123: ++this.pos; return this.finishToken(tt.braceL)
342 case 125: ++this.pos; return this.finishToken(tt.braceR)
343 case 58: ++this.pos; return this.finishToken(tt.colon)
346 if (this.options.ecmaVersion < 6) break
348 return this.finishToken(tt.backQuote)
351 let next = this.input.charCodeAt(this.pos + 1)
352 if (next === 120 || next === 88) return this.readRadixNumber(16) // '0x', '0X' - hex number
353 if (this.options.ecmaVersion >= 6) {
354 if (next === 111 || next === 79) return this.readRadixNumber(8) // '0o', '0O' - octal number
355 if (next === 98 || next === 66) return this.readRadixNumber(2) // '0b', '0B' - binary number
358 // Anything else beginning with a digit is an integer, octal
360 case 49: case 50: case 51: case 52: case 53: case 54: case 55: case 56: case 57: // 1-9
361 return this.readNumber(false)
363 // Quotes produce strings.
364 case 34: case 39: // '"', "'"
365 return this.readString(code)
367 // Operators are parsed inline in tiny state machines. '=' (61) is
368 // often referred to. `finishOp` simply skips the amount of
369 // characters it is given as second argument, and returns a token
370 // of the type given by its first argument.
372 return this.readToken_slash()
374 case 37: case 42: // '%*'
375 return this.readToken_mult_modulo_exp(code)
377 case 124: case 38: // '|&'
378 return this.readToken_pipe_amp(code)
381 return this.readToken_caret()
383 case 43: case 45: // '+-'
384 return this.readToken_plus_min(code)
386 case 60: case 62: // '<>'
387 return this.readToken_lt_gt(code)
389 case 61: case 33: // '=!'
390 return this.readToken_eq_excl(code)
393 return this.readToken_question()
396 return this.finishOp(tt.prefix, 1)
399 return this.readToken_numberSign()
402 this.raise(this.pos, "Unexpected character '" + codePointToString(code) + "'")
405 pp.finishOp = function(type, size) {
406 let str = this.input.slice(this.pos, this.pos + size)
408 return this.finishToken(type, str)
411 pp.readRegexp = function() {
412 let escaped, inClass, start = this.pos
414 if (this.pos >= this.input.length) this.raise(start, "Unterminated regular expression")
415 let ch = this.input.charAt(this.pos)
416 if (lineBreak.test(ch)) this.raise(start, "Unterminated regular expression")
418 if (ch === "[") inClass = true
419 else if (ch === "]" && inClass) inClass = false
420 else if (ch === "/" && !inClass) break
421 escaped = ch === "\\"
422 } else escaped = false
425 let pattern = this.input.slice(start, this.pos)
427 let flagsStart = this.pos
428 let flags = this.readWord1()
429 if (this.containsEsc) this.unexpected(flagsStart)
432 const state = this.regexpState || (this.regexpState = new RegExpValidationState(this))
433 state.reset(start, pattern, flags)
434 this.validateRegExpFlags(state)
435 this.validateRegExpPattern(state)
437 // Create Literal#value property value.
440 value = new RegExp(pattern, flags)
442 // ESTree requires null if it failed to instantiate RegExp object.
443 // https://github.com/estree/estree/blob/a27003adf4fd7bfad44de9cef372a2eacd527b1c/es5.md#regexpliteral
446 return this.finishToken(tt.regexp, {pattern, flags, value})
449 // Read an integer in the given radix. Return null if zero digits
450 // were read, the integer value otherwise. When `len` is given, this
451 // will return `null` unless the integer has exactly `len` digits.
453 pp.readInt = function(radix, len, maybeLegacyOctalNumericLiteral) {
454 // `len` is used for character escape sequences. In that case, disallow separators.
455 const allowSeparators = this.options.ecmaVersion >= 12 && len === undefined
457 // `maybeLegacyOctalNumericLiteral` is true if it doesn't have prefix (0x,0o,0b)
458 // and isn't fraction part nor exponent part. In that case, if the first digit
459 // is zero then disallow separators.
460 const isLegacyOctalNumericLiteral = maybeLegacyOctalNumericLiteral && this.input.charCodeAt(this.pos) === 48
462 let start = this.pos, total = 0, lastCode = 0
463 for (let i = 0, e = len == null ? Infinity : len; i < e; ++i, ++this.pos) {
464 let code = this.input.charCodeAt(this.pos), val
466 if (allowSeparators && code === 95) {
467 if (isLegacyOctalNumericLiteral) this.raiseRecoverable(this.pos, "Numeric separator is not allowed in legacy octal numeric literals")
468 if (lastCode === 95) this.raiseRecoverable(this.pos, "Numeric separator must be exactly one underscore")
469 if (i === 0) this.raiseRecoverable(this.pos, "Numeric separator is not allowed at the first of digits")
474 if (code >= 97) val = code - 97 + 10 // a
475 else if (code >= 65) val = code - 65 + 10 // A
476 else if (code >= 48 && code <= 57) val = code - 48 // 0-9
478 if (val >= radix) break
480 total = total * radix + val
483 if (allowSeparators && lastCode === 95) this.raiseRecoverable(this.pos - 1, "Numeric separator is not allowed at the last of digits")
484 if (this.pos === start || len != null && this.pos - start !== len) return null
489 function stringToNumber(str, isLegacyOctalNumericLiteral) {
490 if (isLegacyOctalNumericLiteral) {
491 return parseInt(str, 8)
494 // `parseFloat(value)` stops parsing at the first numeric separator then returns a wrong value.
495 return parseFloat(str.replace(/_/g, ""))
498 function stringToBigInt(str) {
499 if (typeof BigInt !== "function") {
503 // `BigInt(value)` throws syntax error if the string contains numeric separators.
504 return BigInt(str.replace(/_/g, ""))
507 pp.readRadixNumber = function(radix) {
510 let val = this.readInt(radix)
511 if (val == null) this.raise(this.start + 2, "Expected number in radix " + radix)
512 if (this.options.ecmaVersion >= 11 && this.input.charCodeAt(this.pos) === 110) {
513 val = stringToBigInt(this.input.slice(start, this.pos))
515 } else if (isIdentifierStart(this.fullCharCodeAtPos())) this.raise(this.pos, "Identifier directly after number")
516 return this.finishToken(tt.num, val)
519 // Read an integer, octal integer, or floating-point number.
521 pp.readNumber = function(startsWithDot) {
523 if (!startsWithDot && this.readInt(10, undefined, true) === null) this.raise(start, "Invalid number")
524 let octal = this.pos - start >= 2 && this.input.charCodeAt(start) === 48
525 if (octal && this.strict) this.raise(start, "Invalid number")
526 let next = this.input.charCodeAt(this.pos)
527 if (!octal && !startsWithDot && this.options.ecmaVersion >= 11 && next === 110) {
528 let val = stringToBigInt(this.input.slice(start, this.pos))
530 if (isIdentifierStart(this.fullCharCodeAtPos())) this.raise(this.pos, "Identifier directly after number")
531 return this.finishToken(tt.num, val)
533 if (octal && /[89]/.test(this.input.slice(start, this.pos))) octal = false
534 if (next === 46 && !octal) { // '.'
537 next = this.input.charCodeAt(this.pos)
539 if ((next === 69 || next === 101) && !octal) { // 'eE'
540 next = this.input.charCodeAt(++this.pos)
541 if (next === 43 || next === 45) ++this.pos // '+-'
542 if (this.readInt(10) === null) this.raise(start, "Invalid number")
544 if (isIdentifierStart(this.fullCharCodeAtPos())) this.raise(this.pos, "Identifier directly after number")
546 let val = stringToNumber(this.input.slice(start, this.pos), octal)
547 return this.finishToken(tt.num, val)
550 // Read a string value, interpreting backslash-escapes.
552 pp.readCodePoint = function() {
553 let ch = this.input.charCodeAt(this.pos), code
555 if (ch === 123) { // '{'
556 if (this.options.ecmaVersion < 6) this.unexpected()
557 let codePos = ++this.pos
558 code = this.readHexChar(this.input.indexOf("}", this.pos) - this.pos)
560 if (code > 0x10FFFF) this.invalidStringToken(codePos, "Code point out of bounds")
562 code = this.readHexChar(4)
567 function codePointToString(code) {
569 if (code <= 0xFFFF) return String.fromCharCode(code)
571 return String.fromCharCode((code >> 10) + 0xD800, (code & 1023) + 0xDC00)
574 pp.readString = function(quote) {
575 let out = "", chunkStart = ++this.pos
577 if (this.pos >= this.input.length) this.raise(this.start, "Unterminated string constant")
578 let ch = this.input.charCodeAt(this.pos)
579 if (ch === quote) break
580 if (ch === 92) { // '\'
581 out += this.input.slice(chunkStart, this.pos)
582 out += this.readEscapedChar(false)
583 chunkStart = this.pos
584 } else if (ch === 0x2028 || ch === 0x2029) {
585 if (this.options.ecmaVersion < 10) this.raise(this.start, "Unterminated string constant")
587 if (this.options.locations) {
589 this.lineStart = this.pos
592 if (isNewLine(ch)) this.raise(this.start, "Unterminated string constant")
596 out += this.input.slice(chunkStart, this.pos++)
597 return this.finishToken(tt.string, out)
600 // Reads template string tokens.
602 const INVALID_TEMPLATE_ESCAPE_ERROR = {}
604 pp.tryReadTemplateToken = function() {
605 this.inTemplateElement = true
609 if (err === INVALID_TEMPLATE_ESCAPE_ERROR) {
610 this.readInvalidTemplateToken()
616 this.inTemplateElement = false
619 pp.invalidStringToken = function(position, message) {
620 if (this.inTemplateElement && this.options.ecmaVersion >= 9) {
621 throw INVALID_TEMPLATE_ESCAPE_ERROR
623 this.raise(position, message)
627 pp.readTmplToken = function() {
628 let out = "", chunkStart = this.pos
630 if (this.pos >= this.input.length) this.raise(this.start, "Unterminated template")
631 let ch = this.input.charCodeAt(this.pos)
632 if (ch === 96 || ch === 36 && this.input.charCodeAt(this.pos + 1) === 123) { // '`', '${'
633 if (this.pos === this.start && (this.type === tt.template || this.type === tt.invalidTemplate)) {
636 return this.finishToken(tt.dollarBraceL)
639 return this.finishToken(tt.backQuote)
642 out += this.input.slice(chunkStart, this.pos)
643 return this.finishToken(tt.template, out)
645 if (ch === 92) { // '\'
646 out += this.input.slice(chunkStart, this.pos)
647 out += this.readEscapedChar(true)
648 chunkStart = this.pos
649 } else if (isNewLine(ch)) {
650 out += this.input.slice(chunkStart, this.pos)
654 if (this.input.charCodeAt(this.pos) === 10) ++this.pos
659 out += String.fromCharCode(ch)
662 if (this.options.locations) {
664 this.lineStart = this.pos
666 chunkStart = this.pos
673 // Reads a template token to search for the end, without validating any escape sequences
674 pp.readInvalidTemplateToken = function() {
675 for (; this.pos < this.input.length; this.pos++) {
676 switch (this.input[this.pos]) {
682 if (this.input[this.pos + 1] !== "{") {
688 return this.finishToken(tt.invalidTemplate, this.input.slice(this.start, this.pos))
693 this.raise(this.start, "Unterminated template")
696 // Used to read escaped characters
698 pp.readEscapedChar = function(inTemplate) {
699 let ch = this.input.charCodeAt(++this.pos)
702 case 110: return "\n" // 'n' -> '\n'
703 case 114: return "\r" // 'r' -> '\r'
704 case 120: return String.fromCharCode(this.readHexChar(2)) // 'x'
705 case 117: return codePointToString(this.readCodePoint()) // 'u'
706 case 116: return "\t" // 't' -> '\t'
707 case 98: return "\b" // 'b' -> '\b'
708 case 118: return "\u000b" // 'v' -> '\u000b'
709 case 102: return "\f" // 'f' -> '\f'
710 case 13: if (this.input.charCodeAt(this.pos) === 10) ++this.pos // '\r\n'
712 if (this.options.locations) { this.lineStart = this.pos; ++this.curLine }
717 this.invalidStringToken(
719 "Invalid escape sequence"
723 const codePos = this.pos - 1
725 this.invalidStringToken(
727 "Invalid escape sequence in template string"
733 if (ch >= 48 && ch <= 55) {
734 let octalStr = this.input.substr(this.pos - 1, 3).match(/^[0-7]+/)[0]
735 let octal = parseInt(octalStr, 8)
737 octalStr = octalStr.slice(0, -1)
738 octal = parseInt(octalStr, 8)
740 this.pos += octalStr.length - 1
741 ch = this.input.charCodeAt(this.pos)
742 if ((octalStr !== "0" || ch === 56 || ch === 57) && (this.strict || inTemplate)) {
743 this.invalidStringToken(
744 this.pos - 1 - octalStr.length,
746 ? "Octal literal in template string"
747 : "Octal literal in strict mode"
750 return String.fromCharCode(octal)
753 // Unicode new line characters after \ get removed from output in both
754 // template literals and strings
757 return String.fromCharCode(ch)
761 // Used to read character escape sequences ('\x', '\u', '\U').
763 pp.readHexChar = function(len) {
764 let codePos = this.pos
765 let n = this.readInt(16, len)
766 if (n === null) this.invalidStringToken(codePos, "Bad character escape sequence")
770 // Read an identifier, and return it as a string. Sets `this.containsEsc`
771 // to whether the word contained a '\u' escape.
773 // Incrementally adds only escaped chars, adding other chunks as-is
774 // as a micro-optimization.
776 pp.readWord1 = function() {
777 this.containsEsc = false
778 let word = "", first = true, chunkStart = this.pos
779 let astral = this.options.ecmaVersion >= 6
780 while (this.pos < this.input.length) {
781 let ch = this.fullCharCodeAtPos()
782 if (isIdentifierChar(ch, astral)) {
783 this.pos += ch <= 0xffff ? 1 : 2
784 } else if (ch === 92) { // "\"
785 this.containsEsc = true
786 word += this.input.slice(chunkStart, this.pos)
787 let escStart = this.pos
788 if (this.input.charCodeAt(++this.pos) !== 117) // "u"
789 this.invalidStringToken(this.pos, "Expecting Unicode escape sequence \\uXXXX")
791 let esc = this.readCodePoint()
792 if (!(first ? isIdentifierStart : isIdentifierChar)(esc, astral))
793 this.invalidStringToken(escStart, "Invalid Unicode escape")
794 word += codePointToString(esc)
795 chunkStart = this.pos
801 return word + this.input.slice(chunkStart, this.pos)
804 // Read an identifier or keyword token. Will check for reserved
805 // words when necessary.
807 pp.readWord = function() {
808 let word = this.readWord1()
810 if (this.keywords.test(word)) {
811 type = keywordTypes[word]
813 return this.finishToken(type, word)