Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
80551 views
1
import {isIdentifierStart, isIdentifierChar} from "./identifier"
2
import {types as tt, keywords as keywordTypes} from "./tokentype"
3
import {Parser} from "./state"
4
import {SourceLocation} from "./location"
5
import {lineBreak, lineBreakG, isNewLine, nonASCIIwhitespace} from "./whitespace"
6
7
// Object type used to represent tokens. Note that normally, tokens
8
// simply exist as properties on the parser object. This is only
9
// used for the onToken callback and the external tokenizer.
10
11
export class Token {
12
constructor(p) {
13
this.type = p.type
14
this.value = p.value
15
this.start = p.start
16
this.end = p.end
17
if (p.options.locations)
18
this.loc = new SourceLocation(p, p.startLoc, p.endLoc)
19
if (p.options.ranges)
20
this.range = [p.start, p.end]
21
}
22
}
23
24
// ## Tokenizer
25
26
const pp = Parser.prototype
27
28
// Are we running under Rhino?
29
const isRhino = typeof Packages !== "undefined"
30
31
// Move to the next token
32
33
pp.next = function() {
34
if (this.options.onToken)
35
this.options.onToken(new Token(this))
36
37
this.lastTokEnd = this.end
38
this.lastTokStart = this.start
39
this.lastTokEndLoc = this.endLoc
40
this.lastTokStartLoc = this.startLoc
41
this.nextToken()
42
}
43
44
pp.getToken = function() {
45
this.next()
46
return new Token(this)
47
}
48
49
// If we're in an ES6 environment, make parsers iterable
50
if (typeof Symbol !== "undefined")
51
pp[Symbol.iterator] = function () {
52
let self = this
53
return {next: function () {
54
let token = self.getToken()
55
return {
56
done: token.type === tt.eof,
57
value: token
58
}
59
}}
60
}
61
62
// Toggle strict mode. Re-reads the next number or string to please
63
// pedantic tests (`"use strict"; 010;` should fail).
64
65
pp.setStrict = function(strict) {
66
this.strict = strict
67
if (this.type !== tt.num && this.type !== tt.string) return
68
this.pos = this.start
69
if (this.options.locations) {
70
while (this.pos < this.lineStart) {
71
this.lineStart = this.input.lastIndexOf("\n", this.lineStart - 2) + 1
72
--this.curLine
73
}
74
}
75
this.nextToken()
76
}
77
78
pp.curContext = function() {
79
return this.context[this.context.length - 1]
80
}
81
82
// Read a single token, updating the parser object's token-related
83
// properties.
84
85
pp.nextToken = function() {
86
let curContext = this.curContext()
87
if (!curContext || !curContext.preserveSpace) this.skipSpace()
88
89
this.start = this.pos
90
if (this.options.locations) this.startLoc = this.curPosition()
91
if (this.pos >= this.input.length) return this.finishToken(tt.eof)
92
93
if (curContext.override) return curContext.override(this)
94
else this.readToken(this.fullCharCodeAtPos())
95
}
96
97
pp.readToken = function(code) {
98
// Identifier or keyword. '\uXXXX' sequences are allowed in
99
// identifiers, so '\' also dispatches to that.
100
if (isIdentifierStart(code, this.options.ecmaVersion >= 6) || code === 92 /* '\' */)
101
return this.readWord()
102
103
return this.getTokenFromCode(code)
104
}
105
106
pp.fullCharCodeAtPos = function() {
107
let code = this.input.charCodeAt(this.pos)
108
if (code <= 0xd7ff || code >= 0xe000) return code
109
let next = this.input.charCodeAt(this.pos + 1)
110
return (code << 10) + next - 0x35fdc00
111
}
112
113
pp.skipBlockComment = function() {
114
let startLoc = this.options.onComment && this.options.locations && this.curPosition()
115
let start = this.pos, end = this.input.indexOf("*/", this.pos += 2)
116
if (end === -1) this.raise(this.pos - 2, "Unterminated comment")
117
this.pos = end + 2
118
if (this.options.locations) {
119
lineBreakG.lastIndex = start
120
let match
121
while ((match = lineBreakG.exec(this.input)) && match.index < this.pos) {
122
++this.curLine
123
this.lineStart = match.index + match[0].length
124
}
125
}
126
if (this.options.onComment)
127
this.options.onComment(true, this.input.slice(start + 2, end), start, this.pos,
128
startLoc, this.options.locations && this.curPosition())
129
}
130
131
pp.skipLineComment = function(startSkip) {
132
let start = this.pos
133
let startLoc = this.options.onComment && this.options.locations && this.curPosition()
134
let ch = this.input.charCodeAt(this.pos+=startSkip)
135
while (this.pos < this.input.length && ch !== 10 && ch !== 13 && ch !== 8232 && ch !== 8233) {
136
++this.pos
137
ch = this.input.charCodeAt(this.pos)
138
}
139
if (this.options.onComment)
140
this.options.onComment(false, this.input.slice(start + startSkip, this.pos), start, this.pos,
141
startLoc, this.options.locations && this.curPosition())
142
}
143
144
// Called at the start of the parse and after every token. Skips
145
// whitespace and comments, and.
146
147
pp.skipSpace = function() {
148
while (this.pos < this.input.length) {
149
let ch = this.input.charCodeAt(this.pos)
150
if (ch === 32) { // ' '
151
++this.pos
152
} else if (ch === 13) {
153
++this.pos
154
let next = this.input.charCodeAt(this.pos)
155
if (next === 10) {
156
++this.pos
157
}
158
if (this.options.locations) {
159
++this.curLine
160
this.lineStart = this.pos
161
}
162
} else if (ch === 10 || ch === 8232 || ch === 8233) {
163
++this.pos
164
if (this.options.locations) {
165
++this.curLine
166
this.lineStart = this.pos
167
}
168
} else if (ch > 8 && ch < 14) {
169
++this.pos
170
} else if (ch === 47) { // '/'
171
let next = this.input.charCodeAt(this.pos + 1)
172
if (next === 42) { // '*'
173
this.skipBlockComment()
174
} else if (next === 47) { // '/'
175
this.skipLineComment(2)
176
} else break
177
} else if (ch === 160) { // '\xa0'
178
++this.pos
179
} else if (ch >= 5760 && nonASCIIwhitespace.test(String.fromCharCode(ch))) {
180
++this.pos
181
} else {
182
break
183
}
184
}
185
}
186
187
// Called at the end of every token. Sets `end`, `val`, and
188
// maintains `context` and `exprAllowed`, and skips the space after
189
// the token, so that the next one's `start` will point at the
190
// right position.
191
192
pp.finishToken = function(type, val) {
193
this.end = this.pos
194
if (this.options.locations) this.endLoc = this.curPosition()
195
let prevType = this.type
196
this.type = type
197
this.value = val
198
199
this.updateContext(prevType)
200
}
201
202
// ### Token reading
203
204
// This is the function that is called to fetch the next token. It
205
// is somewhat obscure, because it works in character codes rather
206
// than characters, and because operator parsing has been inlined
207
// into it.
208
//
209
// All in the name of speed.
210
//
211
pp.readToken_dot = function() {
212
let next = this.input.charCodeAt(this.pos + 1)
213
if (next >= 48 && next <= 57) return this.readNumber(true)
214
let next2 = this.input.charCodeAt(this.pos + 2)
215
if (this.options.ecmaVersion >= 6 && next === 46 && next2 === 46) { // 46 = dot '.'
216
this.pos += 3
217
return this.finishToken(tt.ellipsis)
218
} else {
219
++this.pos
220
return this.finishToken(tt.dot)
221
}
222
}
223
224
pp.readToken_slash = function() { // '/'
225
let next = this.input.charCodeAt(this.pos + 1)
226
if (this.exprAllowed) {++this.pos; return this.readRegexp();}
227
if (next === 61) return this.finishOp(tt.assign, 2)
228
return this.finishOp(tt.slash, 1)
229
}
230
231
pp.readToken_mult_modulo = function(code) { // '%*'
232
let next = this.input.charCodeAt(this.pos + 1)
233
if (next === 61) return this.finishOp(tt.assign, 2)
234
return this.finishOp(code === 42 ? tt.star : tt.modulo, 1)
235
}
236
237
pp.readToken_pipe_amp = function(code) { // '|&'
238
let next = this.input.charCodeAt(this.pos + 1)
239
if (next === code) return this.finishOp(code === 124 ? tt.logicalOR : tt.logicalAND, 2)
240
if (next === 61) return this.finishOp(tt.assign, 2)
241
return this.finishOp(code === 124 ? tt.bitwiseOR : tt.bitwiseAND, 1)
242
}
243
244
pp.readToken_caret = function() { // '^'
245
let next = this.input.charCodeAt(this.pos + 1)
246
if (next === 61) return this.finishOp(tt.assign, 2)
247
return this.finishOp(tt.bitwiseXOR, 1)
248
}
249
250
pp.readToken_plus_min = function(code) { // '+-'
251
let next = this.input.charCodeAt(this.pos + 1)
252
if (next === code) {
253
if (next == 45 && this.input.charCodeAt(this.pos + 2) == 62 &&
254
lineBreak.test(this.input.slice(this.lastTokEnd, this.pos))) {
255
// A `-->` line comment
256
this.skipLineComment(3)
257
this.skipSpace()
258
return this.nextToken()
259
}
260
return this.finishOp(tt.incDec, 2)
261
}
262
if (next === 61) return this.finishOp(tt.assign, 2)
263
return this.finishOp(tt.plusMin, 1)
264
}
265
266
pp.readToken_lt_gt = function(code) { // '<>'
267
let next = this.input.charCodeAt(this.pos + 1)
268
let size = 1
269
if (next === code) {
270
size = code === 62 && this.input.charCodeAt(this.pos + 2) === 62 ? 3 : 2
271
if (this.input.charCodeAt(this.pos + size) === 61) return this.finishOp(tt.assign, size + 1)
272
return this.finishOp(tt.bitShift, size)
273
}
274
if (next == 33 && code == 60 && this.input.charCodeAt(this.pos + 2) == 45 &&
275
this.input.charCodeAt(this.pos + 3) == 45) {
276
if (this.inModule) this.unexpected()
277
// `<!--`, an XML-style comment that should be interpreted as a line comment
278
this.skipLineComment(4)
279
this.skipSpace()
280
return this.nextToken()
281
}
282
if (next === 61)
283
size = this.input.charCodeAt(this.pos + 2) === 61 ? 3 : 2
284
return this.finishOp(tt.relational, size)
285
}
286
287
pp.readToken_eq_excl = function(code) { // '=!'
288
let next = this.input.charCodeAt(this.pos + 1)
289
if (next === 61) return this.finishOp(tt.equality, this.input.charCodeAt(this.pos + 2) === 61 ? 3 : 2)
290
if (code === 61 && next === 62 && this.options.ecmaVersion >= 6) { // '=>'
291
this.pos += 2
292
return this.finishToken(tt.arrow)
293
}
294
return this.finishOp(code === 61 ? tt.eq : tt.prefix, 1)
295
}
296
297
pp.getTokenFromCode = function(code) {
298
switch (code) {
299
// The interpretation of a dot depends on whether it is followed
300
// by a digit or another two dots.
301
case 46: // '.'
302
return this.readToken_dot()
303
304
// Punctuation tokens.
305
case 40: ++this.pos; return this.finishToken(tt.parenL)
306
case 41: ++this.pos; return this.finishToken(tt.parenR)
307
case 59: ++this.pos; return this.finishToken(tt.semi)
308
case 44: ++this.pos; return this.finishToken(tt.comma)
309
case 91: ++this.pos; return this.finishToken(tt.bracketL)
310
case 93: ++this.pos; return this.finishToken(tt.bracketR)
311
case 123: ++this.pos; return this.finishToken(tt.braceL)
312
case 125: ++this.pos; return this.finishToken(tt.braceR)
313
case 58: ++this.pos; return this.finishToken(tt.colon)
314
case 63: ++this.pos; return this.finishToken(tt.question)
315
316
case 96: // '`'
317
if (this.options.ecmaVersion < 6) break
318
++this.pos
319
return this.finishToken(tt.backQuote)
320
321
case 48: // '0'
322
let next = this.input.charCodeAt(this.pos + 1)
323
if (next === 120 || next === 88) return this.readRadixNumber(16); // '0x', '0X' - hex number
324
if (this.options.ecmaVersion >= 6) {
325
if (next === 111 || next === 79) return this.readRadixNumber(8); // '0o', '0O' - octal number
326
if (next === 98 || next === 66) return this.readRadixNumber(2); // '0b', '0B' - binary number
327
}
328
// Anything else beginning with a digit is an integer, octal
329
// number, or float.
330
case 49: case 50: case 51: case 52: case 53: case 54: case 55: case 56: case 57: // 1-9
331
return this.readNumber(false)
332
333
// Quotes produce strings.
334
case 34: case 39: // '"', "'"
335
return this.readString(code)
336
337
// Operators are parsed inline in tiny state machines. '=' (61) is
338
// often referred to. `finishOp` simply skips the amount of
339
// characters it is given as second argument, and returns a token
340
// of the type given by its first argument.
341
342
case 47: // '/'
343
return this.readToken_slash()
344
345
case 37: case 42: // '%*'
346
return this.readToken_mult_modulo(code)
347
348
case 124: case 38: // '|&'
349
return this.readToken_pipe_amp(code)
350
351
case 94: // '^'
352
return this.readToken_caret()
353
354
case 43: case 45: // '+-'
355
return this.readToken_plus_min(code)
356
357
case 60: case 62: // '<>'
358
return this.readToken_lt_gt(code)
359
360
case 61: case 33: // '=!'
361
return this.readToken_eq_excl(code)
362
363
case 126: // '~'
364
return this.finishOp(tt.prefix, 1)
365
}
366
367
this.raise(this.pos, "Unexpected character '" + codePointToString(code) + "'")
368
}
369
370
pp.finishOp = function(type, size) {
371
let str = this.input.slice(this.pos, this.pos + size)
372
this.pos += size
373
return this.finishToken(type, str)
374
}
375
376
var regexpUnicodeSupport = false
377
try { new RegExp("\uffff", "u"); regexpUnicodeSupport = true }
378
catch(e) {}
379
380
// Parse a regular expression. Some context-awareness is necessary,
381
// since a '/' inside a '[]' set does not end the expression.
382
383
pp.readRegexp = function() {
384
let escaped, inClass, start = this.pos
385
for (;;) {
386
if (this.pos >= this.input.length) this.raise(start, "Unterminated regular expression")
387
let ch = this.input.charAt(this.pos)
388
if (lineBreak.test(ch)) this.raise(start, "Unterminated regular expression")
389
if (!escaped) {
390
if (ch === "[") inClass = true
391
else if (ch === "]" && inClass) inClass = false
392
else if (ch === "/" && !inClass) break
393
escaped = ch === "\\"
394
} else escaped = false
395
++this.pos
396
}
397
let content = this.input.slice(start, this.pos)
398
++this.pos
399
// Need to use `readWord1` because '\uXXXX' sequences are allowed
400
// here (don't ask).
401
let mods = this.readWord1()
402
let tmp = content
403
if (mods) {
404
let validFlags = /^[gmsiy]*$/
405
if (this.options.ecmaVersion >= 6) validFlags = /^[gmsiyu]*$/
406
if (!validFlags.test(mods)) this.raise(start, "Invalid regular expression flag")
407
if (mods.indexOf('u') >= 0 && !regexpUnicodeSupport) {
408
// Replace each astral symbol and every Unicode escape sequence that
409
// possibly represents an astral symbol or a paired surrogate with a
410
// single ASCII symbol to avoid throwing on regular expressions that
411
// are only valid in combination with the `/u` flag.
412
// Note: replacing with the ASCII symbol `x` might cause false
413
// negatives in unlikely scenarios. For example, `[\u{61}-b]` is a
414
// perfectly valid pattern that is equivalent to `[a-b]`, but it would
415
// be replaced by `[x-b]` which throws an error.
416
tmp = tmp.replace(/\\u([a-fA-F0-9]{4})|\\u\{([0-9a-fA-F]+)\}|[\uD800-\uDBFF][\uDC00-\uDFFF]/g, "x")
417
}
418
}
419
// Detect invalid regular expressions.
420
let value = null
421
// Rhino's regular expression parser is flaky and throws uncatchable exceptions,
422
// so don't do detection if we are running under Rhino
423
if (!isRhino) {
424
try {
425
new RegExp(tmp)
426
} catch (e) {
427
if (e instanceof SyntaxError) this.raise(start, "Error parsing regular expression: " + e.message)
428
this.raise(e)
429
}
430
// Get a regular expression object for this pattern-flag pair, or `null` in
431
// case the current environment doesn't support the flags it uses.
432
try {
433
value = new RegExp(content, mods)
434
} catch (err) {}
435
}
436
return this.finishToken(tt.regexp, {pattern: content, flags: mods, value: value})
437
}
438
439
// Read an integer in the given radix. Return null if zero digits
440
// were read, the integer value otherwise. When `len` is given, this
441
// will return `null` unless the integer has exactly `len` digits.
442
443
pp.readInt = function(radix, len) {
444
let start = this.pos, total = 0
445
for (let i = 0, e = len == null ? Infinity : len; i < e; ++i) {
446
let code = this.input.charCodeAt(this.pos), val
447
if (code >= 97) val = code - 97 + 10; // a
448
else if (code >= 65) val = code - 65 + 10; // A
449
else if (code >= 48 && code <= 57) val = code - 48; // 0-9
450
else val = Infinity
451
if (val >= radix) break
452
++this.pos
453
total = total * radix + val
454
}
455
if (this.pos === start || len != null && this.pos - start !== len) return null
456
457
return total
458
}
459
460
pp.readRadixNumber = function(radix) {
461
this.pos += 2; // 0x
462
let val = this.readInt(radix)
463
if (val == null) this.raise(this.start + 2, "Expected number in radix " + radix)
464
if (isIdentifierStart(this.fullCharCodeAtPos())) this.raise(this.pos, "Identifier directly after number")
465
return this.finishToken(tt.num, val)
466
}
467
468
// Read an integer, octal integer, or floating-point number.
469
470
pp.readNumber = function(startsWithDot) {
471
let start = this.pos, isFloat = false, octal = this.input.charCodeAt(this.pos) === 48
472
if (!startsWithDot && this.readInt(10) === null) this.raise(start, "Invalid number")
473
if (this.input.charCodeAt(this.pos) === 46) {
474
++this.pos
475
this.readInt(10)
476
isFloat = true
477
}
478
let next = this.input.charCodeAt(this.pos)
479
if (next === 69 || next === 101) { // 'eE'
480
next = this.input.charCodeAt(++this.pos)
481
if (next === 43 || next === 45) ++this.pos; // '+-'
482
if (this.readInt(10) === null) this.raise(start, "Invalid number")
483
isFloat = true
484
}
485
if (isIdentifierStart(this.fullCharCodeAtPos())) this.raise(this.pos, "Identifier directly after number")
486
487
let str = this.input.slice(start, this.pos), val
488
if (isFloat) val = parseFloat(str)
489
else if (!octal || str.length === 1) val = parseInt(str, 10)
490
else if (/[89]/.test(str) || this.strict) this.raise(start, "Invalid number")
491
else val = parseInt(str, 8)
492
return this.finishToken(tt.num, val)
493
}
494
495
// Read a string value, interpreting backslash-escapes.
496
497
pp.readCodePoint = function() {
498
let ch = this.input.charCodeAt(this.pos), code
499
500
if (ch === 123) {
501
if (this.options.ecmaVersion < 6) this.unexpected()
502
++this.pos
503
code = this.readHexChar(this.input.indexOf('}', this.pos) - this.pos)
504
++this.pos
505
if (code > 0x10FFFF) this.unexpected()
506
} else {
507
code = this.readHexChar(4)
508
}
509
return code
510
}
511
512
function codePointToString(code) {
513
// UTF-16 Decoding
514
if (code <= 0xFFFF) return String.fromCharCode(code)
515
return String.fromCharCode(((code - 0x10000) >> 10) + 0xD800,
516
((code - 0x10000) & 1023) + 0xDC00)
517
}
518
519
pp.readString = function(quote) {
520
let out = "", chunkStart = ++this.pos
521
for (;;) {
522
if (this.pos >= this.input.length) this.raise(this.start, "Unterminated string constant")
523
let ch = this.input.charCodeAt(this.pos)
524
if (ch === quote) break
525
if (ch === 92) { // '\'
526
out += this.input.slice(chunkStart, this.pos)
527
out += this.readEscapedChar()
528
chunkStart = this.pos
529
} else {
530
if (isNewLine(ch)) this.raise(this.start, "Unterminated string constant")
531
++this.pos
532
}
533
}
534
out += this.input.slice(chunkStart, this.pos++)
535
return this.finishToken(tt.string, out)
536
}
537
538
// Reads template string tokens.
539
540
pp.readTmplToken = function() {
541
let out = "", chunkStart = this.pos
542
for (;;) {
543
if (this.pos >= this.input.length) this.raise(this.start, "Unterminated template")
544
let ch = this.input.charCodeAt(this.pos)
545
if (ch === 96 || ch === 36 && this.input.charCodeAt(this.pos + 1) === 123) { // '`', '${'
546
if (this.pos === this.start && this.type === tt.template) {
547
if (ch === 36) {
548
this.pos += 2
549
return this.finishToken(tt.dollarBraceL)
550
} else {
551
++this.pos
552
return this.finishToken(tt.backQuote)
553
}
554
}
555
out += this.input.slice(chunkStart, this.pos)
556
return this.finishToken(tt.template, out)
557
}
558
if (ch === 92) { // '\'
559
out += this.input.slice(chunkStart, this.pos)
560
out += this.readEscapedChar()
561
chunkStart = this.pos
562
} else if (isNewLine(ch)) {
563
out += this.input.slice(chunkStart, this.pos)
564
++this.pos
565
if (ch === 13 && this.input.charCodeAt(this.pos) === 10) {
566
++this.pos
567
out += "\n"
568
} else {
569
out += String.fromCharCode(ch)
570
}
571
if (this.options.locations) {
572
++this.curLine
573
this.lineStart = this.pos
574
}
575
chunkStart = this.pos
576
} else {
577
++this.pos
578
}
579
}
580
}
581
582
// Used to read escaped characters
583
584
pp.readEscapedChar = function() {
585
let ch = this.input.charCodeAt(++this.pos)
586
let octal = /^[0-7]+/.exec(this.input.slice(this.pos, this.pos + 3))
587
if (octal) octal = octal[0]
588
while (octal && parseInt(octal, 8) > 255) octal = octal.slice(0, -1)
589
if (octal === "0") octal = null
590
++this.pos
591
if (octal) {
592
if (this.strict) this.raise(this.pos - 2, "Octal literal in strict mode")
593
this.pos += octal.length - 1
594
return String.fromCharCode(parseInt(octal, 8))
595
} else {
596
switch (ch) {
597
case 110: return "\n"; // 'n' -> '\n'
598
case 114: return "\r"; // 'r' -> '\r'
599
case 120: return String.fromCharCode(this.readHexChar(2)); // 'x'
600
case 117: return codePointToString(this.readCodePoint()); // 'u'
601
case 116: return "\t"; // 't' -> '\t'
602
case 98: return "\b"; // 'b' -> '\b'
603
case 118: return "\u000b"; // 'v' -> '\u000b'
604
case 102: return "\f"; // 'f' -> '\f'
605
case 48: return "\0"; // 0 -> '\0'
606
case 13: if (this.input.charCodeAt(this.pos) === 10) ++this.pos; // '\r\n'
607
case 10: // ' \n'
608
if (this.options.locations) { this.lineStart = this.pos; ++this.curLine }
609
return ""
610
default: return String.fromCharCode(ch)
611
}
612
}
613
}
614
615
// Used to read character escape sequences ('\x', '\u', '\U').
616
617
pp.readHexChar = function(len) {
618
let n = this.readInt(16, len)
619
if (n === null) this.raise(this.start, "Bad character escape sequence")
620
return n
621
}
622
623
// Used to signal to callers of `readWord1` whether the word
624
// contained any escape sequences. This is needed because words with
625
// escape sequences must not be interpreted as keywords.
626
627
var containsEsc
628
629
// Read an identifier, and return it as a string. Sets `containsEsc`
630
// to whether the word contained a '\u' escape.
631
//
632
// Incrementally adds only escaped chars, adding other chunks as-is
633
// as a micro-optimization.
634
635
pp.readWord1 = function() {
636
containsEsc = false
637
let word = "", first = true, chunkStart = this.pos
638
let astral = this.options.ecmaVersion >= 6
639
while (this.pos < this.input.length) {
640
let ch = this.fullCharCodeAtPos()
641
if (isIdentifierChar(ch, astral)) {
642
this.pos += ch <= 0xffff ? 1 : 2
643
} else if (ch === 92) { // "\"
644
containsEsc = true
645
word += this.input.slice(chunkStart, this.pos)
646
let escStart = this.pos
647
if (this.input.charCodeAt(++this.pos) != 117) // "u"
648
this.raise(this.pos, "Expecting Unicode escape sequence \\uXXXX")
649
++this.pos
650
let esc = this.readCodePoint()
651
if (!(first ? isIdentifierStart : isIdentifierChar)(esc, astral))
652
this.raise(escStart, "Invalid Unicode escape")
653
word += codePointToString(esc)
654
chunkStart = this.pos
655
} else {
656
break
657
}
658
first = false
659
}
660
return word + this.input.slice(chunkStart, this.pos)
661
}
662
663
// Read an identifier or keyword token. Will check for reserved
664
// words when necessary.
665
666
pp.readWord = function() {
667
let word = this.readWord1()
668
let type = tt.name
669
if ((this.options.ecmaVersion >= 6 || !containsEsc) && this.isKeyword(word))
670
type = keywordTypes[word]
671
return this.finishToken(type, word)
672
}
673
674