     1→import { Chars } from '../chars';
     2→import { Context } from '../common';
     3→import { Errors, ParseError } from '../errors';
     4→import { type Parser } from '../parser/parser';
     5→import { getOwnProperty } from '../utilities';
     6→import { descKeywordTable, Token } from './../token';
     7→import { CharFlags, CharTypes, isIdentifierPart, isIdentifierStart, isIdPart } from './charClassifier';
     8→import { advanceChar, consumePossibleSurrogatePair, toHex } from './common';
     9→
    10→/**
    11→ * Scans identifier
    12→ * For identifier doesn't start with unicode escape, but might contain
    13→ * unicode escape after the start.
    14→ *
    15→ * @param parser  Parser object
    16→ * @param context Context masks
    17→ */
    18→export function scanIdentifier(parser: Parser, context: Context, isValidAsKeyword: 0 | 1): Token {
    19→  while (isIdPart[advanceChar(parser)]);
    20→  parser.tokenValue = parser.source.slice(parser.tokenIndex, parser.index);
    21→
    22→  return parser.currentChar !== Chars.Backslash && parser.currentChar <= 0x7e
    23→    ? (getOwnProperty(descKeywordTable, parser.tokenValue) ?? Token.Identifier)
    24→    : scanIdentifierSlowCase(parser, context, 0, isValidAsKeyword);
    25→}
    26→
    27→/**
    28→ * Scans unicode identifier
    29→ * For identifier starts with unicode escape.
    30→ *
    31→ * @param parser  Parser object
    32→ * @param context Context masks
    33→ */
    34→export function scanUnicodeIdentifier(parser: Parser, context: Context): Token {
    35→  const cookedChar = scanIdentifierUnicodeEscape(parser);
    36→  if (!isIdentifierStart(cookedChar)) parser.report(Errors.InvalidUnicodeEscapeSequence);
    37→  parser.tokenValue = String.fromCodePoint(cookedChar);
    38→  return scanIdentifierSlowCase(parser, context, /* hasEscape */ 1, CharTypes[cookedChar] & CharFlags.KeywordCandidate);
    39→}
    40→
    41→/**
    42→ * Scans identifier slow case
    43→ *
    44→ * @param parser  Parser object
    45→ * @param context Context masks
    46→ * @param hasEscape True if contains a unicode sequence
    47→ * @param isValidAsKeyword
    48→ */
    49→export function scanIdentifierSlowCase(
    50→  parser: Parser,
    51→  context: Context,
    52→  hasEscape: 0 | 1,
    53→  isValidAsKeyword: number,
    54→): Token {
    55→  let start = parser.index;
    56→
    57→  while (parser.index < parser.end) {
    58→    if (parser.currentChar === Chars.Backslash) {
    59→      parser.tokenValue += parser.source.slice(start, parser.index);
    60→      hasEscape = 1;
    61→      const code = scanIdentifierUnicodeEscape(parser);
    62→      if (!isIdentifierPart(code)) parser.report(Errors.InvalidUnicodeEscapeSequence);
    63→      isValidAsKeyword = isValidAsKeyword && CharTypes[code] & CharFlags.KeywordCandidate;
    64→      parser.tokenValue += String.fromCodePoint(code);
    65→      start = parser.index;
    66→    } else {
    67→      const merged = consumePossibleSurrogatePair(parser);
    68→      if (merged > 0) {
    69→        if (!isIdentifierPart(merged)) {
    70→          parser.report(Errors.IllegalCharacter, String.fromCodePoint(merged));
    71→        }
    72→        parser.currentChar = merged;
    73→        parser.index++;
    74→        parser.column++;
    75→      } else if (!isIdentifierPart(parser.currentChar)) {
    76→        // Stop
    77→        break;
    78→      }
    79→      advanceChar(parser);
    80→    }
    81→  }
    82→
    83→  if (parser.index <= parser.end) {
    84→    parser.tokenValue += parser.source.slice(start, parser.index);
    85→  }
    86→
    87→  const { length } = parser.tokenValue;
    88→  if (isValidAsKeyword && length >= 2 && length <= 11) {
    89→    const token = getOwnProperty(descKeywordTable, parser.tokenValue);
    90→    if (token === void 0) return Token.Identifier | (hasEscape ? Token.IsEscaped : 0);
    91→    if (!hasEscape) return token;
    92→
    93→    if (token === Token.AwaitKeyword) {
    94→      // await is only reserved word in async functions or modules
    95→      if ((context & (Context.Module | Context.InAwaitContext)) === 0) {
    96→        return token | Token.IsEscaped;
    97→      }
    98→      return Token.EscapedReserved;
    99→    }
   100→
   101→    if (context & Context.Strict) {
   102→      if (token === Token.StaticKeyword) {
   103→        return Token.EscapedFutureReserved;
   104→      }
   105→      if ((token & Token.FutureReserved) === Token.FutureReserved) {
   106→        return Token.EscapedFutureReserved;
   107→      }
   108→      if ((token & Token.Reserved) === Token.Reserved) {
   109→        if (context & Context.AllowEscapedKeyword && (context & Context.InGlobal) === 0) {
   110→          return token | Token.IsEscaped;
   111→        } else {
   112→          return Token.EscapedReserved;
   113→        }
   114→      }
   115→      return Token.AnyIdentifier | Token.IsEscaped;
   116→    }
   117→    if (
   118→      context & Context.AllowEscapedKeyword &&
   119→      (context & Context.InGlobal) === 0 &&
   120→      (token & Token.Reserved) === Token.Reserved
   121→    ) {
   122→      return token | Token.IsEscaped;
   123→    }
   124→    if (token === Token.YieldKeyword) {
   125→      return context & Context.AllowEscapedKeyword
   126→        ? Token.AnyIdentifier | Token.IsEscaped
   127→        : context & Context.InYieldContext
   128→          ? Token.EscapedReserved
   129→          : token | Token.IsEscaped;
   130→    }
   131→
   132→    // async is not reserved; it can be used as a variable name
   133→    // or statement label without restriction
   134→    if (token === Token.AsyncKeyword) {
   135→      // Escaped "async" such as \u0061sync can only be identifier
   136→      // not as "async" keyword
   137→      return Token.AnyIdentifier | Token.IsEscaped;
   138→    }
   139→    if ((token & Token.FutureReserved) === Token.FutureReserved) {
   140→      // In non-strict mode, future reserved can be identifier.
   141→      return token | Token.Contextual | Token.IsEscaped;
   142→    }
   143→    return Token.EscapedReserved;
   144→  }
   145→  return Token.Identifier | (hasEscape ? Token.IsEscaped : 0);
   146→}
   147→
   148→/**
   149→ * Scans private name
   150→ *
   151→ * @param parser  Parser object
   152→ */
   153→export function scanPrivateIdentifier(parser: Parser): Token {
   154→  let char = advanceChar(parser);
   155→  // When nextChar is Backslash "\", it's
   156→  // #\uXXXX unicode escaped private identifier.
   157→  // Unicode escape is scanned next.
   158→  if (char === Chars.Backslash) return Token.PrivateField;
   159→
   160→  const merged = consumePossibleSurrogatePair(parser);
   161→  if (merged) char = merged;
   162→  if (!isIdentifierStart(char)) parser.report(Errors.MissingPrivateIdentifier);
   163→
   164→  return Token.PrivateField;
   165→}
   166→
   167→/**
   168→ * Scans unicode identifier
   169→ *
   170→ * @param parser  Parser object
   171→ */
   172→function scanIdentifierUnicodeEscape(parser: Parser): number {
   173→  // Check for Unicode escape of the form '\uXXXX'
   174→  // and return code point value if valid Unicode escape is found.
   175→  if (parser.source.charCodeAt(parser.index + 1) !== Chars.LowerU) {
   176→    parser.report(Errors.InvalidUnicodeEscapeSequence);
   177→  }
   178→  parser.currentChar = parser.source.charCodeAt((parser.index += 2));
   179→  parser.column += 2;
   180→  return scanUnicodeEscape(parser);
   181→}
   182→
   183→/**
   184→ * Scans unicode escape value
   185→ *
   186→ * @param parser  Parser object
   187→ */
   188→function scanUnicodeEscape(parser: Parser): number {
   189→  // Accept both \uXXXX and \u{XXXXXX}
   190→  let codePoint = 0;
   191→  const char = parser.currentChar;
   192→  // First handle a delimited Unicode escape, e.g. \u{1F4A9}
   193→  if (char === Chars.LeftBrace) {
   194→    const begin = parser.index - 2;
   195→    while (CharTypes[advanceChar(parser)] & CharFlags.Hex) {
   196→      codePoint = (codePoint << 4) | toHex(parser.currentChar);
   197→      if (codePoint > Chars.NonBMPMax)
   198→        throw new ParseError(
   199→          { index: begin, line: parser.line, column: parser.column },
   200→          parser.currentLocation,
   201→          Errors.UnicodeOverflow,
   202→        );
   203→    }
   204→
   205→    // At least 4 characters have to be read
   206→    if ((parser.currentChar as number) !== Chars.RightBrace) {
   207→      throw new ParseError(
   208→        { index: begin, line: parser.line, column: parser.column },
   209→        parser.currentLocation,
   210→        Errors.InvalidHexEscapeSequence,
   211→      );
   212→    }
   213→    advanceChar(parser); // consumes '}'
   214→    return codePoint;
   215→  }
   216→
   217→  if ((CharTypes[char] & CharFlags.Hex) === 0) parser.report(Errors.InvalidHexEscapeSequence); // first one is mandatory
   218→
   219→  const char2 = parser.source.charCodeAt(parser.index + 1);
   220→  if ((CharTypes[char2] & CharFlags.Hex) === 0) parser.report(Errors.InvalidHexEscapeSequence);
   221→  const char3 = parser.source.charCodeAt(parser.index + 2);
   222→  if ((CharTypes[char3] & CharFlags.Hex) === 0) parser.report(Errors.InvalidHexEscapeSequence);
   223→  const char4 = parser.source.charCodeAt(parser.index + 3);
   224→  if ((CharTypes[char4] & CharFlags.Hex) === 0) parser.report(Errors.InvalidHexEscapeSequence);
   225→
   226→  codePoint = (toHex(char) << 12) | (toHex(char2) << 8) | (toHex(char3) << 4) | toHex(char4);
   227→
   228→  parser.currentChar = parser.source.charCodeAt((parser.index += 4));
   229→  parser.column += 4;
   230→  return codePoint;
   231→}
