UNPKG

24.6 kBJavaScriptView Raw
1/* eslint max-len: 0 */
2
3import {input, isFlowEnabled, state} from "../traverser/base";
4import {unexpected} from "../traverser/util";
5import {charCodes} from "../util/charcodes";
6import {IS_IDENTIFIER_CHAR, IS_IDENTIFIER_START} from "../util/identifier";
7import {IS_WHITESPACE} from "../util/whitespace";
8import {ContextualKeyword} from "./keywords";
9import readWord from "./readWord";
10import { TokenType as tt} from "./types";
11
12export var IdentifierRole; (function (IdentifierRole) {
13 const Access = 0; IdentifierRole[IdentifierRole["Access"] = Access] = "Access";
14 const ExportAccess = Access + 1; IdentifierRole[IdentifierRole["ExportAccess"] = ExportAccess] = "ExportAccess";
15 const TopLevelDeclaration = ExportAccess + 1; IdentifierRole[IdentifierRole["TopLevelDeclaration"] = TopLevelDeclaration] = "TopLevelDeclaration";
16 const FunctionScopedDeclaration = TopLevelDeclaration + 1; IdentifierRole[IdentifierRole["FunctionScopedDeclaration"] = FunctionScopedDeclaration] = "FunctionScopedDeclaration";
17 const BlockScopedDeclaration = FunctionScopedDeclaration + 1; IdentifierRole[IdentifierRole["BlockScopedDeclaration"] = BlockScopedDeclaration] = "BlockScopedDeclaration";
18 const ObjectShorthandTopLevelDeclaration = BlockScopedDeclaration + 1; IdentifierRole[IdentifierRole["ObjectShorthandTopLevelDeclaration"] = ObjectShorthandTopLevelDeclaration] = "ObjectShorthandTopLevelDeclaration";
19 const ObjectShorthandFunctionScopedDeclaration = ObjectShorthandTopLevelDeclaration + 1; IdentifierRole[IdentifierRole["ObjectShorthandFunctionScopedDeclaration"] = ObjectShorthandFunctionScopedDeclaration] = "ObjectShorthandFunctionScopedDeclaration";
20 const ObjectShorthandBlockScopedDeclaration = ObjectShorthandFunctionScopedDeclaration + 1; IdentifierRole[IdentifierRole["ObjectShorthandBlockScopedDeclaration"] = ObjectShorthandBlockScopedDeclaration] = "ObjectShorthandBlockScopedDeclaration";
21 const ObjectShorthand = ObjectShorthandBlockScopedDeclaration + 1; IdentifierRole[IdentifierRole["ObjectShorthand"] = ObjectShorthand] = "ObjectShorthand";
22 // Any identifier bound in an import statement, e.g. both A and b from
23 // `import A, * as b from 'A';`
24 const ImportDeclaration = ObjectShorthand + 1; IdentifierRole[IdentifierRole["ImportDeclaration"] = ImportDeclaration] = "ImportDeclaration";
25 const ObjectKey = ImportDeclaration + 1; IdentifierRole[IdentifierRole["ObjectKey"] = ObjectKey] = "ObjectKey";
26 // The `foo` in `import {foo as bar} from "./abc";`.
27 const ImportAccess = ObjectKey + 1; IdentifierRole[IdentifierRole["ImportAccess"] = ImportAccess] = "ImportAccess";
28})(IdentifierRole || (IdentifierRole = {}));
29
30export function isDeclaration(token) {
31 const role = token.identifierRole;
32 return (
33 role === IdentifierRole.TopLevelDeclaration ||
34 role === IdentifierRole.FunctionScopedDeclaration ||
35 role === IdentifierRole.BlockScopedDeclaration ||
36 role === IdentifierRole.ObjectShorthandTopLevelDeclaration ||
37 role === IdentifierRole.ObjectShorthandFunctionScopedDeclaration ||
38 role === IdentifierRole.ObjectShorthandBlockScopedDeclaration
39 );
40}
41
42export function isNonTopLevelDeclaration(token) {
43 const role = token.identifierRole;
44 return (
45 role === IdentifierRole.FunctionScopedDeclaration ||
46 role === IdentifierRole.BlockScopedDeclaration ||
47 role === IdentifierRole.ObjectShorthandFunctionScopedDeclaration ||
48 role === IdentifierRole.ObjectShorthandBlockScopedDeclaration
49 );
50}
51
52export function isTopLevelDeclaration(token) {
53 const role = token.identifierRole;
54 return (
55 role === IdentifierRole.TopLevelDeclaration ||
56 role === IdentifierRole.ObjectShorthandTopLevelDeclaration ||
57 role === IdentifierRole.ImportDeclaration
58 );
59}
60
61export function isBlockScopedDeclaration(token) {
62 const role = token.identifierRole;
63 // Treat top-level declarations as block scope since the distinction doesn't matter here.
64 return (
65 role === IdentifierRole.TopLevelDeclaration ||
66 role === IdentifierRole.BlockScopedDeclaration ||
67 role === IdentifierRole.ObjectShorthandTopLevelDeclaration ||
68 role === IdentifierRole.ObjectShorthandBlockScopedDeclaration
69 );
70}
71
72export function isFunctionScopedDeclaration(token) {
73 const role = token.identifierRole;
74 return (
75 role === IdentifierRole.FunctionScopedDeclaration ||
76 role === IdentifierRole.ObjectShorthandFunctionScopedDeclaration
77 );
78}
79
80export function isObjectShorthandDeclaration(token) {
81 return (
82 token.identifierRole === IdentifierRole.ObjectShorthandTopLevelDeclaration ||
83 token.identifierRole === IdentifierRole.ObjectShorthandBlockScopedDeclaration ||
84 token.identifierRole === IdentifierRole.ObjectShorthandFunctionScopedDeclaration
85 );
86}
87
88// Object type used to represent tokens. Note that normally, tokens
89// simply exist as properties on the parser object. This is only
90// used for the onToken callback and the external tokenizer.
91export class Token {
92 constructor() {
93 this.type = state.type;
94 this.contextualKeyword = state.contextualKeyword;
95 this.start = state.start;
96 this.end = state.end;
97 this.scopeDepth = state.scopeDepth;
98 this.isType = state.isType;
99 this.identifierRole = null;
100 this.shadowsGlobal = false;
101 this.contextId = null;
102 this.rhsEndIndex = null;
103 this.isExpression = false;
104 this.numNullishCoalesceStarts = 0;
105 this.numNullishCoalesceEnds = 0;
106 this.isOptionalChainStart = false;
107 this.isOptionalChainEnd = false;
108 this.subscriptStartIndex = null;
109 this.nullishStartIndex = null;
110 }
111
112
113
114
115
116
117
118
119 // Initially false for all tokens, then may be computed in a follow-up step that does scope
120 // analysis.
121
122 // Initially false for all tokens, but may be set during transform to mark it as containing an
123 // await operation.
124
125
126 // For assignments, the index of the RHS. For export tokens, the end of the export.
127
128 // For class tokens, records if the class is a class expression or a class statement.
129
130 // Number of times to insert a `nullishCoalesce(` snippet before this token.
131
132 // Number of times to insert a `)` snippet after this token.
133
134 // If true, insert an `optionalChain([` snippet before this token.
135
136 // If true, insert a `])` snippet after this token.
137
138 // Tag for `.`, `?.`, `[`, `?.[`, `(`, and `?.(` to denote the "root" token for this
139 // subscript chain. This can be used to determine if this chain is an optional chain.
140
141 // Tag for `??` operators to denote the root token for this nullish coalescing call.
142
143}
144
145// ## Tokenizer
146
147// Move to the next token
148export function next() {
149 state.tokens.push(new Token());
150 nextToken();
151}
152
153// Call instead of next when inside a template, since that needs to be handled differently.
154export function nextTemplateToken() {
155 state.tokens.push(new Token());
156 state.start = state.pos;
157 readTmplToken();
158}
159
160// The tokenizer never parses regexes by default. Instead, the parser is responsible for
161// instructing it to parse a regex when we see a slash at the start of an expression.
162export function retokenizeSlashAsRegex() {
163 if (state.type === tt.assign) {
164 --state.pos;
165 }
166 readRegexp();
167}
168
169export function pushTypeContext(existingTokensInType) {
170 for (let i = state.tokens.length - existingTokensInType; i < state.tokens.length; i++) {
171 state.tokens[i].isType = true;
172 }
173 const oldIsType = state.isType;
174 state.isType = true;
175 return oldIsType;
176}
177
178export function popTypeContext(oldIsType) {
179 state.isType = oldIsType;
180}
181
182export function eat(type) {
183 if (match(type)) {
184 next();
185 return true;
186 } else {
187 return false;
188 }
189}
190
191export function match(type) {
192 return state.type === type;
193}
194
195export function lookaheadType() {
196 const snapshot = state.snapshot();
197 next();
198 const type = state.type;
199 state.restoreFromSnapshot(snapshot);
200 return type;
201}
202
203export class TypeAndKeyword {
204
205
206 constructor(type, contextualKeyword) {
207 this.type = type;
208 this.contextualKeyword = contextualKeyword;
209 }
210}
211
212export function lookaheadTypeAndKeyword() {
213 const snapshot = state.snapshot();
214 next();
215 const type = state.type;
216 const contextualKeyword = state.contextualKeyword;
217 state.restoreFromSnapshot(snapshot);
218 return new TypeAndKeyword(type, contextualKeyword);
219}
220
221// Read a single token, updating the parser object's token-related
222// properties.
223export function nextToken() {
224 skipSpace();
225 state.start = state.pos;
226 if (state.pos >= input.length) {
227 const tokens = state.tokens;
228 // We normally run past the end a bit, but if we're way past the end, avoid an infinite loop.
229 // Also check the token positions rather than the types since sometimes we rewrite the token
230 // type to something else.
231 if (
232 tokens.length >= 2 &&
233 tokens[tokens.length - 1].start >= input.length &&
234 tokens[tokens.length - 2].start >= input.length
235 ) {
236 unexpected("Unexpectedly reached the end of input.");
237 }
238 finishToken(tt.eof);
239 return;
240 }
241 readToken(input.charCodeAt(state.pos));
242}
243
244function readToken(code) {
245 // Identifier or keyword. '\uXXXX' sequences are allowed in
246 // identifiers, so '\' also dispatches to that.
247 if (
248 IS_IDENTIFIER_START[code] ||
249 code === charCodes.backslash ||
250 (code === charCodes.atSign && input.charCodeAt(state.pos + 1) === charCodes.atSign)
251 ) {
252 readWord();
253 } else {
254 getTokenFromCode(code);
255 }
256}
257
258function skipBlockComment() {
259 while (
260 input.charCodeAt(state.pos) !== charCodes.asterisk ||
261 input.charCodeAt(state.pos + 1) !== charCodes.slash
262 ) {
263 state.pos++;
264 if (state.pos > input.length) {
265 unexpected("Unterminated comment", state.pos - 2);
266 return;
267 }
268 }
269 state.pos += 2;
270}
271
272export function skipLineComment(startSkip) {
273 let ch = input.charCodeAt((state.pos += startSkip));
274 if (state.pos < input.length) {
275 while (
276 ch !== charCodes.lineFeed &&
277 ch !== charCodes.carriageReturn &&
278 ch !== charCodes.lineSeparator &&
279 ch !== charCodes.paragraphSeparator &&
280 ++state.pos < input.length
281 ) {
282 ch = input.charCodeAt(state.pos);
283 }
284 }
285}
286
287// Called at the start of the parse and after every token. Skips
288// whitespace and comments.
289export function skipSpace() {
290 while (state.pos < input.length) {
291 const ch = input.charCodeAt(state.pos);
292 switch (ch) {
293 case charCodes.carriageReturn:
294 if (input.charCodeAt(state.pos + 1) === charCodes.lineFeed) {
295 ++state.pos;
296 }
297
298 case charCodes.lineFeed:
299 case charCodes.lineSeparator:
300 case charCodes.paragraphSeparator:
301 ++state.pos;
302 break;
303
304 case charCodes.slash:
305 switch (input.charCodeAt(state.pos + 1)) {
306 case charCodes.asterisk:
307 state.pos += 2;
308 skipBlockComment();
309 break;
310
311 case charCodes.slash:
312 skipLineComment(2);
313 break;
314
315 default:
316 return;
317 }
318 break;
319
320 default:
321 if (IS_WHITESPACE[ch]) {
322 ++state.pos;
323 } else {
324 return;
325 }
326 }
327 }
328}
329
330// Called at the end of every token. Sets various fields, and skips the space after the token, so
331// that the next one's `start` will point at the right position.
332export function finishToken(
333 type,
334 contextualKeyword = ContextualKeyword.NONE,
335) {
336 state.end = state.pos;
337 state.type = type;
338 state.contextualKeyword = contextualKeyword;
339}
340
341// ### Token reading
342
343// This is the function that is called to fetch the next token. It
344// is somewhat obscure, because it works in character codes rather
345// than characters, and because operator parsing has been inlined
346// into it.
347//
348// All in the name of speed.
349function readToken_dot() {
350 const nextChar = input.charCodeAt(state.pos + 1);
351 if (nextChar >= charCodes.digit0 && nextChar <= charCodes.digit9) {
352 readNumber(true);
353 return;
354 }
355
356 if (nextChar === charCodes.dot && input.charCodeAt(state.pos + 2) === charCodes.dot) {
357 state.pos += 3;
358 finishToken(tt.ellipsis);
359 } else {
360 ++state.pos;
361 finishToken(tt.dot);
362 }
363}
364
365function readToken_slash() {
366 const nextChar = input.charCodeAt(state.pos + 1);
367 if (nextChar === charCodes.equalsTo) {
368 finishOp(tt.assign, 2);
369 } else {
370 finishOp(tt.slash, 1);
371 }
372}
373
374function readToken_mult_modulo(code) {
375 // '%*'
376 let tokenType = code === charCodes.asterisk ? tt.star : tt.modulo;
377 let width = 1;
378 let nextChar = input.charCodeAt(state.pos + 1);
379
380 // Exponentiation operator **
381 if (code === charCodes.asterisk && nextChar === charCodes.asterisk) {
382 width++;
383 nextChar = input.charCodeAt(state.pos + 2);
384 tokenType = tt.exponent;
385 }
386
387 // Match *= or %=, disallowing *=> which can be valid in flow.
388 if (
389 nextChar === charCodes.equalsTo &&
390 input.charCodeAt(state.pos + 2) !== charCodes.greaterThan
391 ) {
392 width++;
393 tokenType = tt.assign;
394 }
395
396 finishOp(tokenType, width);
397}
398
399function readToken_pipe_amp(code) {
400 // '|&'
401 const nextChar = input.charCodeAt(state.pos + 1);
402
403 if (nextChar === code) {
404 if (input.charCodeAt(state.pos + 2) === charCodes.equalsTo) {
405 // ||= or &&=
406 finishOp(tt.assign, 3);
407 } else {
408 // || or &&
409 finishOp(code === charCodes.verticalBar ? tt.logicalOR : tt.logicalAND, 2);
410 }
411 return;
412 }
413
414 if (code === charCodes.verticalBar) {
415 // '|>'
416 if (nextChar === charCodes.greaterThan) {
417 finishOp(tt.pipeline, 2);
418 return;
419 } else if (nextChar === charCodes.rightCurlyBrace && isFlowEnabled) {
420 // '|}'
421 finishOp(tt.braceBarR, 2);
422 return;
423 }
424 }
425
426 if (nextChar === charCodes.equalsTo) {
427 finishOp(tt.assign, 2);
428 return;
429 }
430
431 finishOp(code === charCodes.verticalBar ? tt.bitwiseOR : tt.bitwiseAND, 1);
432}
433
434function readToken_caret() {
435 // '^'
436 const nextChar = input.charCodeAt(state.pos + 1);
437 if (nextChar === charCodes.equalsTo) {
438 finishOp(tt.assign, 2);
439 } else {
440 finishOp(tt.bitwiseXOR, 1);
441 }
442}
443
444function readToken_plus_min(code) {
445 // '+-'
446 const nextChar = input.charCodeAt(state.pos + 1);
447
448 if (nextChar === code) {
449 // Tentatively call this a prefix operator, but it might be changed to postfix later.
450 finishOp(tt.preIncDec, 2);
451 return;
452 }
453
454 if (nextChar === charCodes.equalsTo) {
455 finishOp(tt.assign, 2);
456 } else if (code === charCodes.plusSign) {
457 finishOp(tt.plus, 1);
458 } else {
459 finishOp(tt.minus, 1);
460 }
461}
462
463// '<>'
464function readToken_lt_gt(code) {
465 // Avoid right-shift for things like Array<Array<string>>.
466 if (code === charCodes.greaterThan && state.isType) {
467 finishOp(tt.greaterThan, 1);
468 return;
469 }
470 const nextChar = input.charCodeAt(state.pos + 1);
471
472 if (nextChar === code) {
473 const size =
474 code === charCodes.greaterThan && input.charCodeAt(state.pos + 2) === charCodes.greaterThan
475 ? 3
476 : 2;
477 if (input.charCodeAt(state.pos + size) === charCodes.equalsTo) {
478 finishOp(tt.assign, size + 1);
479 return;
480 }
481 finishOp(tt.bitShift, size);
482 return;
483 }
484
485 if (nextChar === charCodes.equalsTo) {
486 // <= | >=
487 finishOp(tt.relationalOrEqual, 2);
488 } else if (code === charCodes.lessThan) {
489 finishOp(tt.lessThan, 1);
490 } else {
491 finishOp(tt.greaterThan, 1);
492 }
493}
494
495function readToken_eq_excl(code) {
496 // '=!'
497 const nextChar = input.charCodeAt(state.pos + 1);
498 if (nextChar === charCodes.equalsTo) {
499 finishOp(tt.equality, input.charCodeAt(state.pos + 2) === charCodes.equalsTo ? 3 : 2);
500 return;
501 }
502 if (code === charCodes.equalsTo && nextChar === charCodes.greaterThan) {
503 // '=>'
504 state.pos += 2;
505 finishToken(tt.arrow);
506 return;
507 }
508 finishOp(code === charCodes.equalsTo ? tt.eq : tt.bang, 1);
509}
510
511function readToken_question() {
512 // '?'
513 const nextChar = input.charCodeAt(state.pos + 1);
514 const nextChar2 = input.charCodeAt(state.pos + 2);
515 if (nextChar === charCodes.questionMark && !state.isType) {
516 if (nextChar2 === charCodes.equalsTo) {
517 // '??='
518 finishOp(tt.assign, 3);
519 } else {
520 // '??'
521 finishOp(tt.nullishCoalescing, 2);
522 }
523 } else if (
524 nextChar === charCodes.dot &&
525 !(nextChar2 >= charCodes.digit0 && nextChar2 <= charCodes.digit9)
526 ) {
527 // '.' not followed by a number
528 state.pos += 2;
529 finishToken(tt.questionDot);
530 } else {
531 ++state.pos;
532 finishToken(tt.question);
533 }
534}
535
536export function getTokenFromCode(code) {
537 switch (code) {
538 case charCodes.numberSign:
539 ++state.pos;
540 finishToken(tt.hash);
541 return;
542
543 // The interpretation of a dot depends on whether it is followed
544 // by a digit or another two dots.
545
546 case charCodes.dot:
547 readToken_dot();
548 return;
549
550 // Punctuation tokens.
551 case charCodes.leftParenthesis:
552 ++state.pos;
553 finishToken(tt.parenL);
554 return;
555 case charCodes.rightParenthesis:
556 ++state.pos;
557 finishToken(tt.parenR);
558 return;
559 case charCodes.semicolon:
560 ++state.pos;
561 finishToken(tt.semi);
562 return;
563 case charCodes.comma:
564 ++state.pos;
565 finishToken(tt.comma);
566 return;
567 case charCodes.leftSquareBracket:
568 ++state.pos;
569 finishToken(tt.bracketL);
570 return;
571 case charCodes.rightSquareBracket:
572 ++state.pos;
573 finishToken(tt.bracketR);
574 return;
575
576 case charCodes.leftCurlyBrace:
577 if (isFlowEnabled && input.charCodeAt(state.pos + 1) === charCodes.verticalBar) {
578 finishOp(tt.braceBarL, 2);
579 } else {
580 ++state.pos;
581 finishToken(tt.braceL);
582 }
583 return;
584
585 case charCodes.rightCurlyBrace:
586 ++state.pos;
587 finishToken(tt.braceR);
588 return;
589
590 case charCodes.colon:
591 if (input.charCodeAt(state.pos + 1) === charCodes.colon) {
592 finishOp(tt.doubleColon, 2);
593 } else {
594 ++state.pos;
595 finishToken(tt.colon);
596 }
597 return;
598
599 case charCodes.questionMark:
600 readToken_question();
601 return;
602 case charCodes.atSign:
603 ++state.pos;
604 finishToken(tt.at);
605 return;
606
607 case charCodes.graveAccent:
608 ++state.pos;
609 finishToken(tt.backQuote);
610 return;
611
612 case charCodes.digit0: {
613 const nextChar = input.charCodeAt(state.pos + 1);
614 // '0x', '0X', '0o', '0O', '0b', '0B'
615 if (
616 nextChar === charCodes.lowercaseX ||
617 nextChar === charCodes.uppercaseX ||
618 nextChar === charCodes.lowercaseO ||
619 nextChar === charCodes.uppercaseO ||
620 nextChar === charCodes.lowercaseB ||
621 nextChar === charCodes.uppercaseB
622 ) {
623 readRadixNumber();
624 return;
625 }
626 }
627 // Anything else beginning with a digit is an integer, octal
628 // number, or float.
629 case charCodes.digit1:
630 case charCodes.digit2:
631 case charCodes.digit3:
632 case charCodes.digit4:
633 case charCodes.digit5:
634 case charCodes.digit6:
635 case charCodes.digit7:
636 case charCodes.digit8:
637 case charCodes.digit9:
638 readNumber(false);
639 return;
640
641 // Quotes produce strings.
642 case charCodes.quotationMark:
643 case charCodes.apostrophe:
644 readString(code);
645 return;
646
647 // Operators are parsed inline in tiny state machines. '=' (charCodes.equalsTo) is
648 // often referred to. `finishOp` simply skips the amount of
649 // characters it is given as second argument, and returns a token
650 // of the type given by its first argument.
651
652 case charCodes.slash:
653 readToken_slash();
654 return;
655
656 case charCodes.percentSign:
657 case charCodes.asterisk:
658 readToken_mult_modulo(code);
659 return;
660
661 case charCodes.verticalBar:
662 case charCodes.ampersand:
663 readToken_pipe_amp(code);
664 return;
665
666 case charCodes.caret:
667 readToken_caret();
668 return;
669
670 case charCodes.plusSign:
671 case charCodes.dash:
672 readToken_plus_min(code);
673 return;
674
675 case charCodes.lessThan:
676 case charCodes.greaterThan:
677 readToken_lt_gt(code);
678 return;
679
680 case charCodes.equalsTo:
681 case charCodes.exclamationMark:
682 readToken_eq_excl(code);
683 return;
684
685 case charCodes.tilde:
686 finishOp(tt.tilde, 1);
687 return;
688
689 default:
690 break;
691 }
692
693 unexpected(`Unexpected character '${String.fromCharCode(code)}'`, state.pos);
694}
695
696function finishOp(type, size) {
697 state.pos += size;
698 finishToken(type);
699}
700
701function readRegexp() {
702 const start = state.pos;
703 let escaped = false;
704 let inClass = false;
705 for (;;) {
706 if (state.pos >= input.length) {
707 unexpected("Unterminated regular expression", start);
708 return;
709 }
710 const code = input.charCodeAt(state.pos);
711 if (escaped) {
712 escaped = false;
713 } else {
714 if (code === charCodes.leftSquareBracket) {
715 inClass = true;
716 } else if (code === charCodes.rightSquareBracket && inClass) {
717 inClass = false;
718 } else if (code === charCodes.slash && !inClass) {
719 break;
720 }
721 escaped = code === charCodes.backslash;
722 }
723 ++state.pos;
724 }
725 ++state.pos;
726 // Need to use `skipWord` because '\uXXXX' sequences are allowed here (don't ask).
727 skipWord();
728
729 finishToken(tt.regexp);
730}
731
732// Read an integer. We allow any valid digit, including hex digits, plus numeric separators, and
733// stop at any other character.
734function readInt() {
735 while (true) {
736 const code = input.charCodeAt(state.pos);
737 if (
738 (code >= charCodes.digit0 && code <= charCodes.digit9) ||
739 (code >= charCodes.lowercaseA && code <= charCodes.lowercaseF) ||
740 (code >= charCodes.uppercaseA && code <= charCodes.uppercaseF) ||
741 code === charCodes.underscore
742 ) {
743 state.pos++;
744 } else {
745 break;
746 }
747 }
748}
749
750function readRadixNumber() {
751 let isBigInt = false;
752
753 state.pos += 2; // 0x
754 readInt();
755
756 if (input.charCodeAt(state.pos) === charCodes.lowercaseN) {
757 ++state.pos;
758 isBigInt = true;
759 }
760
761 if (isBigInt) {
762 finishToken(tt.bigint);
763 return;
764 }
765
766 finishToken(tt.num);
767}
768
769// Read an integer, octal integer, or floating-point number.
770function readNumber(startsWithDot) {
771 let isBigInt = false;
772
773 if (!startsWithDot) {
774 readInt();
775 }
776
777 let nextChar = input.charCodeAt(state.pos);
778 if (nextChar === charCodes.dot) {
779 ++state.pos;
780 readInt();
781 nextChar = input.charCodeAt(state.pos);
782 }
783
784 if (nextChar === charCodes.uppercaseE || nextChar === charCodes.lowercaseE) {
785 nextChar = input.charCodeAt(++state.pos);
786 if (nextChar === charCodes.plusSign || nextChar === charCodes.dash) {
787 ++state.pos;
788 }
789 readInt();
790 nextChar = input.charCodeAt(state.pos);
791 }
792
793 if (nextChar === charCodes.lowercaseN) {
794 ++state.pos;
795 isBigInt = true;
796 }
797
798 if (isBigInt) {
799 finishToken(tt.bigint);
800 return;
801 }
802 finishToken(tt.num);
803}
804
805function readString(quote) {
806 state.pos++;
807 for (;;) {
808 if (state.pos >= input.length) {
809 unexpected("Unterminated string constant");
810 return;
811 }
812 const ch = input.charCodeAt(state.pos);
813 if (ch === charCodes.backslash) {
814 state.pos++;
815 } else if (ch === quote) {
816 break;
817 }
818 state.pos++;
819 }
820 state.pos++;
821 finishToken(tt.string);
822}
823
824// Reads template string tokens.
825function readTmplToken() {
826 for (;;) {
827 if (state.pos >= input.length) {
828 unexpected("Unterminated template");
829 return;
830 }
831 const ch = input.charCodeAt(state.pos);
832 if (
833 ch === charCodes.graveAccent ||
834 (ch === charCodes.dollarSign && input.charCodeAt(state.pos + 1) === charCodes.leftCurlyBrace)
835 ) {
836 if (state.pos === state.start && match(tt.template)) {
837 if (ch === charCodes.dollarSign) {
838 state.pos += 2;
839 finishToken(tt.dollarBraceL);
840 return;
841 } else {
842 ++state.pos;
843 finishToken(tt.backQuote);
844 return;
845 }
846 }
847 finishToken(tt.template);
848 return;
849 }
850 if (ch === charCodes.backslash) {
851 state.pos++;
852 }
853 state.pos++;
854 }
855}
856
857// Skip to the end of the current word. Note that this is the same as the snippet at the end of
858// readWord, but calling skipWord from readWord seems to slightly hurt performance from some rough
859// measurements.
860export function skipWord() {
861 while (state.pos < input.length) {
862 const ch = input.charCodeAt(state.pos);
863 if (IS_IDENTIFIER_CHAR[ch]) {
864 state.pos++;
865 } else if (ch === charCodes.backslash) {
866 // \u
867 state.pos += 2;
868 if (input.charCodeAt(state.pos) === charCodes.leftCurlyBrace) {
869 while (
870 state.pos < input.length &&
871 input.charCodeAt(state.pos) !== charCodes.rightCurlyBrace
872 ) {
873 state.pos++;
874 }
875 state.pos++;
876 }
877 } else {
878 break;
879 }
880 }
881}