// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

/**
 * Defines the tokens that are produced by the scanner, used by the parser, and
 * referenced from the [AST structure](ast.dart).
 */
import 'dart:collection';

import '../base/syntactic_entity.dart';
import 'string_utilities.dart';
import 'token_constants.dart';

const int NO_PRECEDENCE = 0;
const int ASSIGNMENT_PRECEDENCE = 1;
const int CASCADE_PRECEDENCE = 2;
const int CONDITIONAL_PRECEDENCE = 3;
const int IF_NULL_PRECEDENCE = 4;
const int LOGICAL_OR_PRECEDENCE = 5;
const int LOGICAL_AND_PRECEDENCE = 6;
const int EQUALITY_PRECEDENCE = 7;
const int RELATIONAL_PRECEDENCE = 8;
const int BITWISE_OR_PRECEDENCE = 9;
const int BITWISE_XOR_PRECEDENCE = 10;
const int BITWISE_AND_PRECEDENCE = 11;
const int SHIFT_PRECEDENCE = 12;
const int ADDITIVE_PRECEDENCE = 13;
const int MULTIPLICATIVE_PRECEDENCE = 14;
const int PREFIX_PRECEDENCE = 15;
const int POSTFIX_PRECEDENCE = 16;
const int SELECTOR_PRECEDENCE = 17;

/**
 * The opening half of a grouping pair of tokens. This is used for curly
 * brackets ('{'), parentheses ('('), and square brackets ('[').
 */
class BeginToken extends SimpleToken {
  /**
   * The token that corresponds to this token.
   */
  Token? endToken;

  /**
   * Initialize a newly created token to have the given [type] at the given
   * [offset].
   */
  BeginToken(TokenType type, int offset, [CommentToken? precedingComment])
      : super(type, offset, precedingComment) {
    assert(type == TokenType.LT ||
        type == TokenType.OPEN_CURLY_BRACKET ||
        type == TokenType.OPEN_PAREN ||
        type == TokenType.OPEN_SQUARE_BRACKET ||
        type == TokenType.STRING_INTERPOLATION_EXPRESSION);
  }

  @override
  Token? get endGroup => endToken;

  /**
   * Set the token that corresponds to this token.
   */
  set endGroup(Token? token) {
    endToken = token;
  }
}

/**
 * A token representing a comment.
 */
class CommentToken extends StringToken {
  /**
   * The token that contains this comment.
   */
  SimpleToken? parent;

  /**
   * Initialize a newly created token to represent a token of the given [type]
   * with the given [value] at the given [offset].
   */
  CommentToken(super.type, super.value, super.offset);
}

/**
 * A documentation comment token.
 */
class DocumentationCommentToken extends CommentToken {
  /**
   * Initialize a newly created token to represent a token of the given [type]
   * with the given [value] at the given [offset].
   */
  DocumentationCommentToken(super.type, super.value, super.offset);
}

enum KeywordStyle {
  reserved,
  builtIn,
  pseudo,
}

/**
 * The keywords in the Dart programming language.
 *
 * Clients may not extend, implement or mix-in this class.
 */
class Keyword extends TokenType {
  static const Keyword ABSTRACT = const Keyword(
      /* index = */ 82, "abstract", "ABSTRACT", KeywordStyle.builtIn,
      isModifier: true);

  static const Keyword AS = const Keyword(
      /* index = */ 83, "as", "AS", KeywordStyle.builtIn,
      precedence: RELATIONAL_PRECEDENCE);

  static const Keyword ASSERT = const Keyword(
      /* index = */ 84, "assert", "ASSERT", KeywordStyle.reserved);

  static const Keyword ASYNC =
      const Keyword(/* index = */ 85, "async", "ASYNC", KeywordStyle.pseudo);

  static const Keyword AUGMENT = const Keyword(
      /* index = */ 86, "augment", "AUGMENT", KeywordStyle.builtIn,
      isModifier: true);

  static const Keyword AWAIT =
      const Keyword(/* index = */ 87, "await", "AWAIT", KeywordStyle.pseudo);

  static const Keyword BASE =
      const Keyword(/* index = */ 88, "base", "BASE", KeywordStyle.pseudo);

  static const Keyword BREAK =
      const Keyword(/* index = */ 89, "break", "BREAK", KeywordStyle.reserved);

  static const Keyword CASE =
      const Keyword(/* index = */ 90, "case", "CASE", KeywordStyle.reserved);

  static const Keyword CATCH =
      const Keyword(/* index = */ 91, "catch", "CATCH", KeywordStyle.reserved);

  static const Keyword CLASS = const Keyword(
      /* index = */ 92, "class", "CLASS", KeywordStyle.reserved,
      isTopLevelKeyword: true);

  static const Keyword CONST = const Keyword(
      /* index = */ 93, "const", "CONST", KeywordStyle.reserved,
      isModifier: true);

  static const Keyword CONTINUE = const Keyword(
      /* index = */ 94, "continue", "CONTINUE", KeywordStyle.reserved);

  static const Keyword COVARIANT = const Keyword(
      /* index = */ 95, "covariant", "COVARIANT", KeywordStyle.builtIn,
      isModifier: true);

  static const Keyword DEFAULT = const Keyword(
      /* index = */ 96, "default", "DEFAULT", KeywordStyle.reserved);

  static const Keyword DEFERRED = const Keyword(
      /* index = */ 97, "deferred", "DEFERRED", KeywordStyle.builtIn);

  static const Keyword DO =
      const Keyword(/* index = */ 98, "do", "DO", KeywordStyle.reserved);

  static const Keyword DYNAMIC = const Keyword(
      /* index = */ 99, "dynamic", "DYNAMIC", KeywordStyle.builtIn);

  static const Keyword ELSE =
      const Keyword(/* index = */ 100, "else", "ELSE", KeywordStyle.reserved);

  static const Keyword ENUM = const Keyword(
      /* index = */ 101, "enum", "ENUM", KeywordStyle.reserved,
      isTopLevelKeyword: true);

  static const Keyword EXPORT = const Keyword(
      /* index = */ 102, "export", "EXPORT", KeywordStyle.builtIn,
      isTopLevelKeyword: true);

  static const Keyword EXTENDS = const Keyword(
      /* index = */ 103, "extends", "EXTENDS", KeywordStyle.reserved);

  static const Keyword EXTENSION = const Keyword(
      /* index = */ 104, "extension", "EXTENSION", KeywordStyle.builtIn,
      isTopLevelKeyword: true);

  static const Keyword EXTERNAL = const Keyword(
      /* index = */ 105, "external", "EXTERNAL", KeywordStyle.builtIn,
      isModifier: true);

  static const Keyword FACTORY = const Keyword(
      /* index = */ 106, "factory", "FACTORY", KeywordStyle.builtIn);

  static const Keyword FALSE =
      const Keyword(/* index = */ 107, "false", "FALSE", KeywordStyle.reserved);

  static const Keyword FINAL = const Keyword(
      /* index = */ 108, "final", "FINAL", KeywordStyle.reserved,
      isModifier: true);

  static const Keyword FINALLY = const Keyword(
      /* index = */ 109, "finally", "FINALLY", KeywordStyle.reserved);

  static const Keyword FOR =
      const Keyword(/* index = */ 110, "for", "FOR", KeywordStyle.reserved);

  static const Keyword FUNCTION = const Keyword(
      /* index = */ 111, "Function", "FUNCTION", KeywordStyle.builtIn);

  static const Keyword GET =
      const Keyword(/* index = */ 112, "get", "GET", KeywordStyle.builtIn);

  static const Keyword HIDE =
      const Keyword(/* index = */ 113, "hide", "HIDE", KeywordStyle.pseudo);

  static const Keyword IF =
      const Keyword(/* index = */ 114, "if", "IF", KeywordStyle.reserved);

  static const Keyword IMPLEMENTS = const Keyword(
      /* index = */ 115, "implements", "IMPLEMENTS", KeywordStyle.builtIn);

  static const Keyword IMPORT = const Keyword(
      /* index = */ 116, "import", "IMPORT", KeywordStyle.builtIn,
      isTopLevelKeyword: true);

  static const Keyword IN =
      const Keyword(/* index = */ 117, "in", "IN", KeywordStyle.reserved);

  static const Keyword INOUT =
      const Keyword(/* index = */ 118, "inout", "INOUT", KeywordStyle.pseudo);

  static const Keyword INTERFACE = const Keyword(
      /* index = */ 119, "interface", "INTERFACE", KeywordStyle.builtIn);

  static const Keyword IS = const Keyword(
      /* index = */ 120, "is", "IS", KeywordStyle.reserved,
      precedence: RELATIONAL_PRECEDENCE);

  static const Keyword LATE = const Keyword(
      /* index = */ 121, "late", "LATE", KeywordStyle.builtIn,
      isModifier: true);

  static const Keyword LIBRARY = const Keyword(
      /* index = */ 122, "library", "LIBRARY", KeywordStyle.builtIn,
      isTopLevelKeyword: true);

  static const Keyword MIXIN = const Keyword(
      /* index = */ 123, "mixin", "MIXIN", KeywordStyle.builtIn,
      isTopLevelKeyword: true);

  static const Keyword NATIVE =
      const Keyword(/* index = */ 124, "native", "NATIVE", KeywordStyle.pseudo);

  static const Keyword NEW =
      const Keyword(/* index = */ 125, "new", "NEW", KeywordStyle.reserved);

  static const Keyword NULL =
      const Keyword(/* index = */ 126, "null", "NULL", KeywordStyle.reserved);

  static const Keyword OF =
      const Keyword(/* index = */ 127, "of", "OF", KeywordStyle.pseudo);

  static const Keyword ON =
      const Keyword(/* index = */ 128, "on", "ON", KeywordStyle.pseudo);

  static const Keyword OPERATOR = const Keyword(
      /* index = */ 129, "operator", "OPERATOR", KeywordStyle.builtIn);

  static const Keyword OUT =
      const Keyword(/* index = */ 130, "out", "OUT", KeywordStyle.pseudo);

  static const Keyword PART = const Keyword(
      /* index = */ 131, "part", "PART", KeywordStyle.builtIn,
      isTopLevelKeyword: true);

  static const Keyword PATCH =
      const Keyword(/* index = */ 132, "patch", "PATCH", KeywordStyle.pseudo);

  static const Keyword REQUIRED = const Keyword(
      /* index = */ 133, "required", "REQUIRED", KeywordStyle.builtIn,
      isModifier: true);

  static const Keyword RETHROW = const Keyword(
      /* index = */ 134, "rethrow", "RETHROW", KeywordStyle.reserved);

  static const Keyword RETURN = const Keyword(
      /* index = */ 135, "return", "RETURN", KeywordStyle.reserved);

  static const Keyword SEALED =
      const Keyword(/* index = */ 136, "sealed", "SEALED", KeywordStyle.pseudo);

  static const Keyword SET =
      const Keyword(/* index = */ 137, "set", "SET", KeywordStyle.builtIn);

  static const Keyword SHOW =
      const Keyword(/* index = */ 138, "show", "SHOW", KeywordStyle.pseudo);

  static const Keyword SOURCE =
      const Keyword(/* index = */ 139, "source", "SOURCE", KeywordStyle.pseudo);

  static const Keyword STATIC = const Keyword(
      /* index = */ 140, "static", "STATIC", KeywordStyle.builtIn,
      isModifier: true);

  static const Keyword SUPER =
      const Keyword(/* index = */ 141, "super", "SUPER", KeywordStyle.reserved);

  static const Keyword SWITCH = const Keyword(
      /* index = */ 142, "switch", "SWITCH", KeywordStyle.reserved);

  static const Keyword SYNC =
      const Keyword(/* index = */ 143, "sync", "SYNC", KeywordStyle.pseudo);

  static const Keyword THIS =
      const Keyword(/* index = */ 144, "this", "THIS", KeywordStyle.reserved);

  static const Keyword THROW =
      const Keyword(/* index = */ 145, "throw", "THROW", KeywordStyle.reserved);

  static const Keyword TRUE =
      const Keyword(/* index = */ 146, "true", "TRUE", KeywordStyle.reserved);

  static const Keyword TRY =
      const Keyword(/* index = */ 147, "try", "TRY", KeywordStyle.reserved);

  static const Keyword TYPEDEF = const Keyword(
      /* index = */ 148, "typedef", "TYPEDEF", KeywordStyle.builtIn,
      isTopLevelKeyword: true);

  static const Keyword VAR = const Keyword(
      /* index = */ 149, "var", "VAR", KeywordStyle.reserved,
      isModifier: true);

  static const Keyword VOID =
      const Keyword(/* index = */ 150, "void", "VOID", KeywordStyle.reserved);

  static const Keyword WHEN =
      const Keyword(/* index = */ 151, "when", 'WHEN', KeywordStyle.pseudo);

  static const Keyword WHILE =
      const Keyword(/* index = */ 152, "while", "WHILE", KeywordStyle.reserved);

  static const Keyword WITH =
      const Keyword(/* index = */ 153, "with", "WITH", KeywordStyle.reserved);

  static const Keyword YIELD =
      const Keyword(/* index = */ 154, "yield", "YIELD", KeywordStyle.pseudo);

  static const List<Keyword> values = const <Keyword>[
    ABSTRACT,
    AS,
    ASSERT,
    ASYNC,
    AUGMENT,
    AWAIT,
    BASE,
    BREAK,
    CASE,
    CATCH,
    CLASS,
    CONST,
    CONTINUE,
    COVARIANT,
    DEFAULT,
    DEFERRED,
    DO,
    DYNAMIC,
    ELSE,
    ENUM,
    EXPORT,
    EXTENDS,
    EXTENSION,
    EXTERNAL,
    FACTORY,
    FALSE,
    FINAL,
    FINALLY,
    FOR,
    FUNCTION,
    GET,
    HIDE,
    IF,
    IMPLEMENTS,
    IMPORT,
    IN,
    INOUT,
    INTERFACE,
    IS,
    LATE,
    LIBRARY,
    MIXIN,
    NATIVE,
    NEW,
    NULL,
    OF,
    ON,
    OPERATOR,
    OUT,
    PART,
    PATCH,
    REQUIRED,
    RETHROW,
    RETURN,
    SEALED,
    SET,
    SHOW,
    SOURCE,
    STATIC,
    SUPER,
    SWITCH,
    SYNC,
    THIS,
    THROW,
    TRUE,
    TRY,
    TYPEDEF,
    VAR,
    VOID,
    WHEN,
    WHILE,
    WITH,
    YIELD,
  ];

  /**
   * A table mapping the lexemes of keywords to the corresponding keyword.
   */
  static final Map<String, Keyword> keywords = _createKeywordMap();

  final KeywordStyle keywordStyle;

  /**
   * Initialize a newly created keyword.
   */
  const Keyword(int index, String lexeme, String name, this.keywordStyle,
      {bool isModifier = false,
      bool isTopLevelKeyword = false,
      int precedence = NO_PRECEDENCE})
      : super(index, lexeme, name, precedence, KEYWORD_TOKEN,
            isModifier: isModifier, isTopLevelKeyword: isTopLevelKeyword);

  @override
  bool get isBuiltIn => keywordStyle == KeywordStyle.builtIn;

  @override
  bool get isPseudo => keywordStyle == KeywordStyle.pseudo;

  bool get isBuiltInOrPseudo => isBuiltIn || isPseudo;

  @override
  bool get isReservedWord => keywordStyle == KeywordStyle.reserved;

  /**
   * The name of the keyword type.
   */
  @override
  String get name => lexeme.toUpperCase();

  @override
  String toString() => name;

  /**
   * Create a table mapping the lexemes of keywords to the corresponding keyword
   * and return the table that was created.
   */
  static Map<String, Keyword> _createKeywordMap() {
    LinkedHashMap<String, Keyword> result =
        new LinkedHashMap<String, Keyword>();
    for (Keyword keyword in values) {
      result[keyword.lexeme] = keyword;
    }
    return result;
  }
}

/**
 * A token representing a keyword in the language.
 */
class KeywordToken extends SimpleToken {
  @override
  Keyword get keyword => type as Keyword;

  /**
   * Initialize a newly created token to represent the given [keyword] at the
   * given [offset].
   */
  KeywordToken(super.keyword, super.offset, [super.precedingComment]);

  @override
  bool get isIdentifier => keyword.isPseudo || keyword.isBuiltIn;

  @override
  bool get isKeyword => true;

  @override
  bool get isKeywordOrIdentifier => true;

  @override
  Object value() => keyword;
}

/**
 * A specialized comment token representing a language version
 * (e.g. '// @dart = 2.1').
 */
class LanguageVersionToken extends CommentToken {
  /**
   * The major language version.
   */
  final int major;

  /**
   * The minor language version.
   */
  final int minor;

  LanguageVersionToken.from(String text, int offset, this.major, this.minor)
      : super(TokenType.SINGLE_LINE_COMMENT, text, offset);
}

/**
 * A token that was scanned from the input. Each token knows which tokens
 * precede and follow it, acting as a link in a doubly linked list of tokens.
 */
class SimpleToken implements Token {
  /**
   * The type of the token.
   */
  @override
  TokenType get type => _tokenTypesByIndex[_typeAndOffset & 0xff];

  /**
   * The index of the type.
   */
  @override
  int get typeIndex => _typeAndOffset & 0xff;

  /**
   * The offset from the beginning of the file to the first character in the
   * token.
   */
  @override
  int get offset => (_typeAndOffset >> 8) - 1;

  /**
   * Set the offset from the beginning of the file to the first character in
   * the token to the given [offset].
   */
  @override
  void set offset(int value) {
    assert(_tokenTypesByIndex.length == 256);
    // See https://github.com/dart-lang/sdk/issues/50048 for details.
    assert(value >= -1);
    _typeAndOffset = ((value + 1) << 8) | (_typeAndOffset & 0xff);
  }

  /**
   * The previous token in the token stream.
   */
  @override
  Token? previous;

  @override
  Token? next;

  /**
   * The first comment in the list of comments that precede this token.
   */
  CommentToken? _precedingComment;

  /**
   * The combined encoding of token type and offset.
   */
  int _typeAndOffset;

  /**
   * Initialize a newly created token to have the given [type] and [offset].
   */
  SimpleToken(TokenType type, int offset, [this._precedingComment])
      : _typeAndOffset = (((offset + 1) << 8) | type.index) {
    // See https://github.com/dart-lang/sdk/issues/50048 for details.
    assert(offset >= -1);

    // Assert the encoding of the [type] is fully reversible.
    assert(type.index < 256 && _tokenTypesByIndex.length == 256);
    assert(identical(offset, this.offset));
    assert(identical(type, this.type), '$type != ${this.type}');

    _setCommentParent(_precedingComment);
  }

  @override
  int get charCount => length;

  @override
  int get charOffset => offset;

  @override
  int get charEnd => end;

  @override
  Token? get beforeSynthetic => null;

  @override
  set beforeSynthetic(Token? previous) {
    // ignored
  }

  @override
  int get end => offset + length;

  @override
  Token? get endGroup => null;

  @override
  bool get isEof => type == TokenType.EOF;

  @override
  bool get isIdentifier => false;

  @override
  bool get isKeyword => false;

  @override
  bool get isKeywordOrIdentifier => isIdentifier;

  @override
  bool get isModifier => type.isModifier;

  @override
  bool get isOperator => type.isOperator;

  @override
  bool get isSynthetic => length == 0;

  @override
  bool get isTopLevelKeyword => type.isTopLevelKeyword;

  @override
  bool get isUserDefinableOperator => type.isUserDefinableOperator;

  @override
  Keyword? get keyword => null;

  @override
  int get kind => type.kind;

  @override
  int get length => lexeme.length;

  @override
  String get lexeme => type.lexeme;

  @override
  CommentToken? get precedingComments => _precedingComment;

  void set precedingComments(CommentToken? comment) {
    _precedingComment = comment;
    _setCommentParent(_precedingComment);
  }

  @override
  String? get stringValue => type.stringValue;

  @override
  bool matchesAny(List<TokenType> types) {
    // [type] is a getter that accesses [_tokenTypesByIndex]:
    TokenType type = this.type;
    for (TokenType t in types) {
      if (type == t) {
        return true;
      }
    }
    return false;
  }

  @override
  Token setNext(Token token) {
    next = token;
    token.previous = this;
    token.beforeSynthetic = this;
    return token;
  }

  @override
  Token? setNextWithoutSettingPrevious(Token? token) {
    next = token;
    return token;
  }

  @override
  String toString() => lexeme;

  @override
  Object value() => lexeme;

  /**
   * Sets the `parent` property to `this` for the given [comment] and all the
   * next tokens.
   */
  @pragma("vm:prefer-inline")
  void _setCommentParent(CommentToken? comment) {
    while (comment != null) {
      comment.parent = this;
      comment = comment.next as CommentToken?;
    }
  }
}

/**
 * A token whose value is independent of it's type.
 */
class StringToken extends SimpleToken {
  /**
   * The lexeme represented by this token.
   */
  final String _value;

  /**
   * Initialize a newly created token to represent a token of the given [type]
   * with the given [value] at the given [offset].
   */
  StringToken(super.type, String value, super.offset, [super.precedingComment])
      : _value = StringUtilities.intern(value);

  @override
  bool get isIdentifier => kind == IDENTIFIER_TOKEN;

  @override
  String get lexeme => _value;

  @override
  String value() => _value;
}

/**
 * A synthetic begin token.
 */
class SyntheticBeginToken extends BeginToken {
  /**
   * Initialize a newly created token to have the given [type] at the given
   * [offset].
   */
  SyntheticBeginToken(super.type, super.offset, [super.precedingComment]);

  @override
  Token? beforeSynthetic;

  @override
  bool get isSynthetic => true;

  @override
  int get length => 0;
}

/**
 * A synthetic keyword token.
 */
class SyntheticKeywordToken extends KeywordToken {
  /**
   * Initialize a newly created token to represent the given [keyword] at the
   * given [offset].
   */
  SyntheticKeywordToken(super.keyword, super.offset);

  @override
  Token? beforeSynthetic;

  @override
  int get length => 0;
}

/**
 * A token whose value is independent of it's type.
 */
class SyntheticStringToken extends StringToken {
  final int? _length;

  /**
   * Initialize a newly created token to represent a token of the given [type]
   * with the given [value] at the given [offset]. If the [length] is
   * not specified, then it defaults to the length of [value].
   */
  SyntheticStringToken(super.type, super.value, super.offset, [this._length]);

  @override
  Token? beforeSynthetic;

  @override
  bool get isSynthetic => true;

  @override
  int get length => _length ?? super.length;
}

/**
 * A synthetic token.
 */
class SyntheticToken extends SimpleToken {
  SyntheticToken(super.type, super.offset);

  @override
  Token? beforeSynthetic;

  @override
  bool get isSynthetic => true;

  @override
  int get length => 0;
}

/// A token used to replace another token in the stream, while still keeping the
/// old token around (in [replacedToken]). Automatically sets the offset and
/// precedingComments from the data available on [replacedToken].
class ReplacementToken extends SyntheticToken {
  /// The token that this token replaces. This will normally be the token
  /// representing what the user actually wrote.
  final Token replacedToken;

  ReplacementToken(TokenType type, this.replacedToken)
      : super(type, replacedToken.offset) {
    precedingComments = replacedToken.precedingComments;
  }

  @override
  Token? beforeSynthetic;

  @override
  bool get isSynthetic => true;

  @override
  int get length => 0;
}

/**
 * A token that was scanned from the input. Each token knows which tokens
 * precede and follow it, acting as a link in a doubly linked list of tokens.
 *
 * Clients may not extend, implement or mix-in this class.
 */
abstract class Token implements SyntacticEntity {
  /**
   * Initialize a newly created token to have the given [type] and [offset].
   */
  factory Token(TokenType type, int offset, [CommentToken? precedingComment]) =
      SimpleToken;

  /**
   * Initialize a newly created end-of-file token to have the given [offset].
   */
  factory Token.eof(int offset, [CommentToken? precedingComments]) {
    Token eof = new SimpleToken(TokenType.EOF, offset, precedingComments);
    // EOF points to itself so there's always infinite look-ahead.
    eof.previous = eof;
    eof.next = eof;
    return eof;
  }

  /**
   * The number of characters parsed by this token.
   */
  int get charCount;

  /**
   * The character offset of the start of this token within the source text.
   */
  int get charOffset;

  /**
   * The character offset of the end of this token within the source text.
   */
  int get charEnd;

  /**
   * The token before this synthetic token,
   * or `null` if this is not a synthetic `)`, `]`, `}`, or `>` token.
   */
  Token? get beforeSynthetic;

  /**
   * Set token before this synthetic `)`, `]`, `}`, or `>` token,
   * and ignored otherwise.
   */
  set beforeSynthetic(Token? previous);

  @override
  int get end;

  /**
   * The token that corresponds to this token, or `null` if this token is not
   * the first of a pair of matching tokens (such as parentheses).
   */
  Token? get endGroup => null;

  /**
   * Return `true` if this token represents an end of file.
   */
  bool get isEof;

  /**
   * True if this token is an identifier. Some keywords allowed as identifiers,
   * see implementation in [KeywordToken].
   */
  bool get isIdentifier;

  /**
   * True if this token is a keyword. Some keywords allowed as identifiers,
   * see implementation in [KeywordToken].
   */
  bool get isKeyword;

  /**
   * True if this token is a keyword or an identifier.
   */
  bool get isKeywordOrIdentifier;

  /**
   * Return `true` if this token is a modifier such as `abstract` or `const`.
   */
  bool get isModifier;

  /**
   * Return `true` if this token represents an operator.
   */
  bool get isOperator;

  /**
   * Return `true` if this token is a synthetic token. A synthetic token is a
   * token that was introduced by the parser in order to recover from an error
   * in the code.
   */
  bool get isSynthetic;

  /**
   * Return `true` if this token is a keyword starting a top level declaration
   * such as `class`, `enum`, `import`, etc.
   */
  bool get isTopLevelKeyword;

  /**
   * Return `true` if this token represents an operator that can be defined by
   * users.
   */
  bool get isUserDefinableOperator;

  /**
   * Return the keyword, if a keyword token, or `null` otherwise.
   */
  Keyword? get keyword;

  /**
   * The kind enum of this token as determined by its [type].
   */
  int get kind;

  @override
  int get length;

  /**
   * Return the lexeme that represents this token.
   *
   * For [StringToken]s the [lexeme] includes the quotes, explicit escapes, etc.
   */
  String get lexeme;

  /**
   * Return the next token in the token stream.
   */
  Token? get next;

  /**
   * Return the next token in the token stream.
   */
  void set next(Token? next);

  @override
  int get offset;

  /**
   * Set the offset from the beginning of the file to the first character in
   * the token to the given [offset].
   */
  void set offset(int offset);

  /**
   * Return the first comment in the list of comments that precede this token,
   * or `null` if there are no comments preceding this token. Additional
   * comments can be reached by following the token stream using [next] until
   * `null` is returned.
   *
   * For example, if the original contents were `/* one */ /* two */ id`, then
   * the first preceding comment token will have a lexeme of `/* one */` and
   * the next comment token will have a lexeme of `/* two */`.
   */
  CommentToken? get precedingComments;

  /**
   * Return the previous token in the token stream.
   */
  Token? get previous;

  /**
   * Set the previous token in the token stream to the given [token].
   */
  void set previous(Token? token);

  /**
   * For symbol and keyword tokens, returns the string value represented by this
   * token. For [StringToken]s this method returns [:null:].
   *
   * For symbol [Token]s and [KeywordToken]s, the string value is a compile-time
   * constant originating in the [TokenType] or in the [Keyword] instance.
   * This allows testing for keywords and symbols using [:identical:], e.g.,
   * [:identical('class', token.value):].
   *
   * Note that returning [:null:] for string tokens is important to identify
   * symbols and keywords, we cannot use [lexeme] instead. The string literal
   *   "$a($b"
   * produces ..., SymbolToken($), StringToken(a), StringToken((), ...
   *
   * After parsing the identifier 'a', the parser tests for a function
   * declaration using [:identical(next.stringValue, '('):], which (rightfully)
   * returns false because stringValue returns [:null:].
   */
  String? get stringValue;

  /**
   * Return the type of the token.
   */
  TokenType get type;

  /**
   * Return the index of the type of the token.
   */
  int get typeIndex;

  /**
   * Return `true` if this token has any one of the given [types].
   */
  bool matchesAny(List<TokenType> types);

  /**
   * Set the next token in the token stream to the given [token]. This has the
   * side-effect of setting this token to be the previous token for the given
   * token. Return the token that was passed in.
   */
  Token setNext(Token token);

  /**
   * Set the next token in the token stream to the given token without changing
   * which token is the previous token for the given token. Return the token
   * that was passed in.
   */
  Token? setNextWithoutSettingPrevious(Token? token);

  /**
   * Returns a textual representation of this token to be used for debugging
   * purposes. The resulting string might contain information about the
   * structure of the token, for example 'StringToken(foo)' for the identifier
   * token 'foo'.
   *
   * Use [lexeme] for the text actually parsed by the token.
   */
  @override
  String toString();

  /**
   * Return the value of this token. For keyword tokens, this is the keyword
   * associated with the token, for other tokens it is the lexeme associated
   * with the token.
   */
  Object value();

  /**
   * Compare the given tokens to find the token that appears first in the
   * source being parsed. That is, return the left-most of all of the tokens.
   * Return the token with the smallest offset, or `null` if all of the
   * tokens are `null`.
   */
  static Token? lexicallyFirst(
      [Token? t1, Token? t2, Token? t3, Token? t4, Token? t5]) {
    Token? result = t1;
    if (result == null || t2 != null && t2.offset < result.offset) {
      result = t2;
    }
    if (result == null || t3 != null && t3.offset < result.offset) {
      result = t3;
    }
    if (result == null || t4 != null && t4.offset < result.offset) {
      result = t4;
    }
    if (result == null || t5 != null && t5.offset < result.offset) {
      result = t5;
    }
    return result;
  }
}

/**
 * The classes (or groups) of tokens with a similar use.
 */
class TokenClass {
  /**
   * A value used to indicate that the token type is not part of any specific
   * class of token.
   */
  static const TokenClass NO_CLASS = const TokenClass('NO_CLASS');

  /**
   * A value used to indicate that the token type is an additive operator.
   */
  static const TokenClass ADDITIVE_OPERATOR =
      const TokenClass('ADDITIVE_OPERATOR', ADDITIVE_PRECEDENCE);

  /**
   * A value used to indicate that the token type is an assignment operator.
   */
  static const TokenClass ASSIGNMENT_OPERATOR =
      const TokenClass('ASSIGNMENT_OPERATOR', ASSIGNMENT_PRECEDENCE);

  /**
   * A value used to indicate that the token type is a bitwise-and operator.
   */
  static const TokenClass BITWISE_AND_OPERATOR =
      const TokenClass('BITWISE_AND_OPERATOR', BITWISE_AND_PRECEDENCE);

  /**
   * A value used to indicate that the token type is a bitwise-or operator.
   */
  static const TokenClass BITWISE_OR_OPERATOR =
      const TokenClass('BITWISE_OR_OPERATOR', BITWISE_OR_PRECEDENCE);

  /**
   * A value used to indicate that the token type is a bitwise-xor operator.
   */
  static const TokenClass BITWISE_XOR_OPERATOR =
      const TokenClass('BITWISE_XOR_OPERATOR', BITWISE_XOR_PRECEDENCE);

  /**
   * A value used to indicate that the token type is a cascade operator.
   */
  static const TokenClass CASCADE_OPERATOR =
      const TokenClass('CASCADE_OPERATOR', CASCADE_PRECEDENCE);

  /**
   * A value used to indicate that the token type is a conditional operator.
   */
  static const TokenClass CONDITIONAL_OPERATOR =
      const TokenClass('CONDITIONAL_OPERATOR', CONDITIONAL_PRECEDENCE);

  /**
   * A value used to indicate that the token type is an equality operator.
   */
  static const TokenClass EQUALITY_OPERATOR =
      const TokenClass('EQUALITY_OPERATOR', EQUALITY_PRECEDENCE);

  /**
   * A value used to indicate that the token type is an if-null operator.
   */
  static const TokenClass IF_NULL_OPERATOR =
      const TokenClass('IF_NULL_OPERATOR', IF_NULL_PRECEDENCE);

  /**
   * A value used to indicate that the token type is a logical-and operator.
   */
  static const TokenClass LOGICAL_AND_OPERATOR =
      const TokenClass('LOGICAL_AND_OPERATOR', LOGICAL_AND_PRECEDENCE);

  /**
   * A value used to indicate that the token type is a logical-or operator.
   */
  static const TokenClass LOGICAL_OR_OPERATOR =
      const TokenClass('LOGICAL_OR_OPERATOR', LOGICAL_OR_PRECEDENCE);

  /**
   * A value used to indicate that the token type is a multiplicative operator.
   */
  static const TokenClass MULTIPLICATIVE_OPERATOR =
      const TokenClass('MULTIPLICATIVE_OPERATOR', MULTIPLICATIVE_PRECEDENCE);

  /**
   * A value used to indicate that the token type is a relational operator.
   */
  static const TokenClass RELATIONAL_OPERATOR =
      const TokenClass('RELATIONAL_OPERATOR', RELATIONAL_PRECEDENCE);

  /**
   * A value used to indicate that the token type is a shift operator.
   */
  static const TokenClass SHIFT_OPERATOR =
      const TokenClass('SHIFT_OPERATOR', SHIFT_PRECEDENCE);

  /**
   * A value used to indicate that the token type is a unary operator.
   */
  static const TokenClass UNARY_POSTFIX_OPERATOR =
      const TokenClass('UNARY_POSTFIX_OPERATOR', POSTFIX_PRECEDENCE);

  /**
   * A value used to indicate that the token type is a unary operator.
   */
  static const TokenClass UNARY_PREFIX_OPERATOR =
      const TokenClass('UNARY_PREFIX_OPERATOR', PREFIX_PRECEDENCE);

  /**
   * The name of the token class.
   */
  final String name;

  /**
   * The precedence of tokens of this class, or `0` if the such tokens do not
   * represent an operator.
   */
  final int precedence;

  /**
   * Initialize a newly created class of tokens to have the given [name] and
   * [precedence].
   */
  const TokenClass(this.name, [this.precedence = NO_PRECEDENCE]);

  @override
  String toString() => name;
}

/**
 * The types of tokens that can be returned by the scanner.
 *
 * Clients may not extend, implement or mix-in this class.
 */
class TokenType {
  static const TokenType UNUSED = const TokenType(
      /* index = */ 255, '', 'UNUSED', NO_PRECEDENCE, EOF_TOKEN);

  /**
   * The type of the token that marks the start or end of the input.
   */
  static const TokenType EOF =
      const TokenType(/* index = */ 0, '', 'EOF', NO_PRECEDENCE, EOF_TOKEN);

  static const TokenType DOUBLE = const TokenType(
      /* index = */ 1, 'double', 'DOUBLE', NO_PRECEDENCE, DOUBLE_TOKEN,
      stringValueShouldBeNull: true);

  static const TokenType DOUBLE_WITH_SEPARATORS = const TokenType(
      /* index = */ 2,
      'double',
      'DOUBLE_WITH_SEPARATORS',
      NO_PRECEDENCE,
      DOUBLE_TOKEN,
      stringValueShouldBeNull: true);

  static const TokenType HEXADECIMAL = const TokenType(/* index = */ 3,
      'hexadecimal', 'HEXADECIMAL', NO_PRECEDENCE, HEXADECIMAL_TOKEN,
      stringValueShouldBeNull: true);

  static const TokenType HEXADECIMAL_WITH_SEPARATORS = const TokenType(
      /* index = */ 4,
      'hexadecimal',
      'HEXADECIMAL_WITH_SEPARATORS',
      NO_PRECEDENCE,
      HEXADECIMAL_TOKEN,
      stringValueShouldBeNull: true);

  static const TokenType IDENTIFIER = const TokenType(/* index = */ 5,
      'identifier', 'IDENTIFIER', NO_PRECEDENCE, IDENTIFIER_TOKEN,
      stringValueShouldBeNull: true);

  static const TokenType INT = const TokenType(
      /* index = */ 6, 'int', 'INT', NO_PRECEDENCE, INT_TOKEN,
      stringValueShouldBeNull: true);

  static const TokenType INT_WITH_SEPARATORS = const TokenType(
      /* index = */ 7, 'int', 'INT_WITH_SEPARATORS', NO_PRECEDENCE, INT_TOKEN,
      stringValueShouldBeNull: true);

  static const TokenType MULTI_LINE_COMMENT = const TokenType(/* index = */ 8,
      'comment', 'MULTI_LINE_COMMENT', NO_PRECEDENCE, COMMENT_TOKEN,
      stringValueShouldBeNull: true);

  static const TokenType SCRIPT_TAG = const TokenType(
      /* index = */ 9, 'script', 'SCRIPT_TAG', NO_PRECEDENCE, SCRIPT_TOKEN);

  static const TokenType SINGLE_LINE_COMMENT = const TokenType(/* index = */ 10,
      'comment', 'SINGLE_LINE_COMMENT', NO_PRECEDENCE, COMMENT_TOKEN,
      stringValueShouldBeNull: true);

  static const TokenType STRING = const TokenType(
      /* index = */ 11, 'string', 'STRING', NO_PRECEDENCE, STRING_TOKEN,
      stringValueShouldBeNull: true);

  static const TokenType AMPERSAND = const TokenType(/* index = */ 12, '&',
      'AMPERSAND', BITWISE_AND_PRECEDENCE, AMPERSAND_TOKEN,
      isOperator: true, isBinaryOperator: true, isUserDefinableOperator: true);

  static const TokenType AMPERSAND_AMPERSAND = const TokenType(
      /* index = */ 13,
      '&&',
      'AMPERSAND_AMPERSAND',
      LOGICAL_AND_PRECEDENCE,
      AMPERSAND_AMPERSAND_TOKEN,
      isOperator: true,
      isBinaryOperator: true);

  // This is not yet part of the language and not supported by the scanner.
  static const TokenType AMPERSAND_AMPERSAND_EQ = const TokenType(
      /* index = */ 14,
      '&&=',
      'AMPERSAND_AMPERSAND_EQ',
      ASSIGNMENT_PRECEDENCE,
      AMPERSAND_AMPERSAND_EQ_TOKEN,
      binaryOperatorOfCompoundAssignment: TokenType.AMPERSAND_AMPERSAND,
      isOperator: true);

  static const TokenType AMPERSAND_EQ = const TokenType(/* index = */ 15, '&=',
      'AMPERSAND_EQ', ASSIGNMENT_PRECEDENCE, AMPERSAND_EQ_TOKEN,
      binaryOperatorOfCompoundAssignment: TokenType.AMPERSAND,
      isOperator: true);

  static const TokenType AT =
      const TokenType(/* index = */ 16, '@', 'AT', NO_PRECEDENCE, AT_TOKEN);

  static const TokenType BANG = const TokenType(
      /* index = */ 17, '!', 'BANG', PREFIX_PRECEDENCE, BANG_TOKEN,
      isOperator: true);

  static const TokenType BANG_EQ = const TokenType(
      /* index = */ 18, '!=', 'BANG_EQ', EQUALITY_PRECEDENCE, BANG_EQ_TOKEN,
      isOperator: true);

  static const TokenType BANG_EQ_EQ = const TokenType(/* index = */ 19, '!==',
      'BANG_EQ_EQ', EQUALITY_PRECEDENCE, BANG_EQ_EQ_TOKEN);

  static const TokenType BAR = const TokenType(
      /* index = */ 20, '|', 'BAR', BITWISE_OR_PRECEDENCE, BAR_TOKEN,
      isOperator: true, isBinaryOperator: true, isUserDefinableOperator: true);

  static const TokenType BAR_BAR = const TokenType(
      /* index = */ 21, '||', 'BAR_BAR', LOGICAL_OR_PRECEDENCE, BAR_BAR_TOKEN,
      isOperator: true, isBinaryOperator: true);

  // This is not yet part of the language and not supported by the scanner.
  static const TokenType BAR_BAR_EQ = const TokenType(/* index = */ 22, '||=',
      'BAR_BAR_EQ', ASSIGNMENT_PRECEDENCE, BAR_BAR_EQ_TOKEN,
      binaryOperatorOfCompoundAssignment: TokenType.BAR_BAR, isOperator: true);

  static const TokenType BAR_EQ = const TokenType(
      /* index = */ 23, '|=', 'BAR_EQ', ASSIGNMENT_PRECEDENCE, BAR_EQ_TOKEN,
      binaryOperatorOfCompoundAssignment: TokenType.BAR, isOperator: true);

  static const TokenType COLON = const TokenType(
      /* index = */ 24, ':', 'COLON', NO_PRECEDENCE, COLON_TOKEN);

  static const TokenType COMMA = const TokenType(
      /* index = */ 25, ',', 'COMMA', NO_PRECEDENCE, COMMA_TOKEN);

  static const TokenType CARET = const TokenType(
      /* index = */ 26, '^', 'CARET', BITWISE_XOR_PRECEDENCE, CARET_TOKEN,
      isOperator: true, isBinaryOperator: true, isUserDefinableOperator: true);

  static const TokenType CARET_EQ = const TokenType(
      /* index = */ 27, '^=', 'CARET_EQ', ASSIGNMENT_PRECEDENCE, CARET_EQ_TOKEN,
      binaryOperatorOfCompoundAssignment: TokenType.CARET, isOperator: true);

  static const TokenType CLOSE_CURLY_BRACKET = const TokenType(/* index = */ 28,
      '}', 'CLOSE_CURLY_BRACKET', NO_PRECEDENCE, CLOSE_CURLY_BRACKET_TOKEN);

  static const TokenType CLOSE_PAREN = const TokenType(
      /* index = */ 29, ')', 'CLOSE_PAREN', NO_PRECEDENCE, CLOSE_PAREN_TOKEN);

  static const TokenType CLOSE_SQUARE_BRACKET = const TokenType(
      /* index = */ 30,
      ']',
      'CLOSE_SQUARE_BRACKET',
      NO_PRECEDENCE,
      CLOSE_SQUARE_BRACKET_TOKEN);

  static const TokenType EQ = const TokenType(
      /* index = */ 31, '=', 'EQ', ASSIGNMENT_PRECEDENCE, EQ_TOKEN,
      isOperator: true);

  static const TokenType EQ_EQ = const TokenType(
      /* index = */ 32, '==', 'EQ_EQ', EQUALITY_PRECEDENCE, EQ_EQ_TOKEN,
      isOperator: true, isBinaryOperator: true, isUserDefinableOperator: true);

  /// The `===` operator is not supported in the Dart language
  /// but is parsed as such by the scanner to support better recovery
  /// when a JavaScript code snippet is pasted into a Dart file.
  static const TokenType EQ_EQ_EQ = const TokenType(
      /* index = */ 33, '===', 'EQ_EQ_EQ', EQUALITY_PRECEDENCE, EQ_EQ_EQ_TOKEN);

  static const TokenType FUNCTION = const TokenType(
      /* index = */ 34, '=>', 'FUNCTION', NO_PRECEDENCE, FUNCTION_TOKEN);

  static const TokenType GT = const TokenType(
      /* index = */ 35, '>', 'GT', RELATIONAL_PRECEDENCE, GT_TOKEN,
      isOperator: true, isBinaryOperator: true, isUserDefinableOperator: true);

  static const TokenType GT_EQ = const TokenType(
      /* index = */ 36, '>=', 'GT_EQ', RELATIONAL_PRECEDENCE, GT_EQ_TOKEN,
      isOperator: true, isBinaryOperator: true, isUserDefinableOperator: true);

  static const TokenType GT_GT = const TokenType(
      /* index = */ 37, '>>', 'GT_GT', SHIFT_PRECEDENCE, GT_GT_TOKEN,
      isOperator: true, isBinaryOperator: true, isUserDefinableOperator: true);

  static const TokenType GT_GT_EQ = const TokenType(/* index = */ 38, '>>=',
      'GT_GT_EQ', ASSIGNMENT_PRECEDENCE, GT_GT_EQ_TOKEN,
      binaryOperatorOfCompoundAssignment: TokenType.GT_GT, isOperator: true);

  static const TokenType GT_GT_GT = const TokenType(
      /* index = */ 39, '>>>', 'GT_GT_GT', SHIFT_PRECEDENCE, GT_GT_GT_TOKEN,
      isOperator: true, isBinaryOperator: true, isUserDefinableOperator: true);

  static const TokenType GT_GT_GT_EQ = const TokenType(/* index = */ 40, '>>>=',
      'GT_GT_GT_EQ', ASSIGNMENT_PRECEDENCE, GT_GT_GT_EQ_TOKEN,
      binaryOperatorOfCompoundAssignment: TokenType.GT_GT_GT, isOperator: true);

  static const TokenType HASH =
      const TokenType(/* index = */ 41, '#', 'HASH', NO_PRECEDENCE, HASH_TOKEN);

  static const TokenType INDEX = const TokenType(
      /* index = */ 42, '[]', 'INDEX', SELECTOR_PRECEDENCE, INDEX_TOKEN,
      isOperator: true, isUserDefinableOperator: true);

  static const TokenType INDEX_EQ = const TokenType(
      /* index = */ 43, '[]=', 'INDEX_EQ', NO_PRECEDENCE, INDEX_EQ_TOKEN,
      isOperator: true, isUserDefinableOperator: true);

  static const TokenType LT = const TokenType(
      /* index = */ 44, '<', 'LT', RELATIONAL_PRECEDENCE, LT_TOKEN,
      isOperator: true, isBinaryOperator: true, isUserDefinableOperator: true);

  static const TokenType LT_EQ = const TokenType(
      /* index = */ 45, '<=', 'LT_EQ', RELATIONAL_PRECEDENCE, LT_EQ_TOKEN,
      isOperator: true, isBinaryOperator: true, isUserDefinableOperator: true);

  static const TokenType LT_LT = const TokenType(
      /* index = */ 46, '<<', 'LT_LT', SHIFT_PRECEDENCE, LT_LT_TOKEN,
      isOperator: true, isBinaryOperator: true, isUserDefinableOperator: true);

  static const TokenType LT_LT_EQ = const TokenType(/* index = */ 47, '<<=',
      'LT_LT_EQ', ASSIGNMENT_PRECEDENCE, LT_LT_EQ_TOKEN,
      binaryOperatorOfCompoundAssignment: TokenType.LT_LT, isOperator: true);

  static const TokenType MINUS = const TokenType(
      /* index = */ 48, '-', 'MINUS', ADDITIVE_PRECEDENCE, MINUS_TOKEN,
      isOperator: true, isBinaryOperator: true, isUserDefinableOperator: true);

  static const TokenType MINUS_EQ = const TokenType(
      /* index = */ 49, '-=', 'MINUS_EQ', ASSIGNMENT_PRECEDENCE, MINUS_EQ_TOKEN,
      binaryOperatorOfCompoundAssignment: TokenType.MINUS, isOperator: true);

  static const TokenType MINUS_MINUS = const TokenType(/* index = */ 50, '--',
      'MINUS_MINUS', POSTFIX_PRECEDENCE, MINUS_MINUS_TOKEN,
      isOperator: true);

  static const TokenType OPEN_CURLY_BRACKET = const TokenType(/* index = */ 51,
      '{', 'OPEN_CURLY_BRACKET', NO_PRECEDENCE, OPEN_CURLY_BRACKET_TOKEN);

  static const TokenType OPEN_PAREN = const TokenType(/* index = */ 52, '(',
      'OPEN_PAREN', SELECTOR_PRECEDENCE, OPEN_PAREN_TOKEN);

  static const TokenType OPEN_SQUARE_BRACKET = const TokenType(
      /* index = */ 53,
      '[',
      'OPEN_SQUARE_BRACKET',
      SELECTOR_PRECEDENCE,
      OPEN_SQUARE_BRACKET_TOKEN);

  static const TokenType PERCENT = const TokenType(/* index = */ 54, '%',
      'PERCENT', MULTIPLICATIVE_PRECEDENCE, PERCENT_TOKEN,
      isOperator: true, isBinaryOperator: true, isUserDefinableOperator: true);

  static const TokenType PERCENT_EQ = const TokenType(/* index = */ 55, '%=',
      'PERCENT_EQ', ASSIGNMENT_PRECEDENCE, PERCENT_EQ_TOKEN,
      binaryOperatorOfCompoundAssignment: TokenType.PERCENT, isOperator: true);

  static const TokenType PERIOD = const TokenType(
      /* index = */ 56, '.', 'PERIOD', SELECTOR_PRECEDENCE, PERIOD_TOKEN);

  static const TokenType PERIOD_PERIOD = const TokenType(/* index = */ 57, '..',
      'PERIOD_PERIOD', CASCADE_PRECEDENCE, PERIOD_PERIOD_TOKEN,
      isOperator: true);

  static const TokenType PLUS = const TokenType(
      /* index = */ 58, '+', 'PLUS', ADDITIVE_PRECEDENCE, PLUS_TOKEN,
      isOperator: true, isBinaryOperator: true, isUserDefinableOperator: true);

  static const TokenType PLUS_EQ = const TokenType(
      /* index = */ 59, '+=', 'PLUS_EQ', ASSIGNMENT_PRECEDENCE, PLUS_EQ_TOKEN,
      binaryOperatorOfCompoundAssignment: TokenType.PLUS, isOperator: true);

  static const TokenType PLUS_PLUS = const TokenType(
      /* index = */ 60, '++', 'PLUS_PLUS', POSTFIX_PRECEDENCE, PLUS_PLUS_TOKEN,
      isOperator: true);

  static const TokenType QUESTION = const TokenType(
      /* index = */ 61, '?', 'QUESTION', CONDITIONAL_PRECEDENCE, QUESTION_TOKEN,
      isOperator: true);

  static const TokenType QUESTION_PERIOD = const TokenType(/* index = */ 62,
      '?.', 'QUESTION_PERIOD', SELECTOR_PRECEDENCE, QUESTION_PERIOD_TOKEN,
      isOperator: true);

  static const TokenType QUESTION_QUESTION = const TokenType(/* index = */ 63,
      '??', 'QUESTION_QUESTION', IF_NULL_PRECEDENCE, QUESTION_QUESTION_TOKEN,
      isOperator: true, isBinaryOperator: true);

  static const TokenType QUESTION_QUESTION_EQ = const TokenType(
      /* index = */ 64,
      '??=',
      'QUESTION_QUESTION_EQ',
      ASSIGNMENT_PRECEDENCE,
      QUESTION_QUESTION_EQ_TOKEN,
      binaryOperatorOfCompoundAssignment: TokenType.QUESTION_QUESTION,
      isOperator: true);

  static const TokenType SEMICOLON = const TokenType(
      /* index = */ 65, ';', 'SEMICOLON', NO_PRECEDENCE, SEMICOLON_TOKEN);

  static const TokenType SLASH = const TokenType(
      /* index = */ 66, '/', 'SLASH', MULTIPLICATIVE_PRECEDENCE, SLASH_TOKEN,
      isOperator: true, isBinaryOperator: true, isUserDefinableOperator: true);

  static const TokenType SLASH_EQ = const TokenType(
      /* index = */ 67, '/=', 'SLASH_EQ', ASSIGNMENT_PRECEDENCE, SLASH_EQ_TOKEN,
      binaryOperatorOfCompoundAssignment: TokenType.SLASH, isOperator: true);

  static const TokenType STAR = const TokenType(
      /* index = */ 68, '*', 'STAR', MULTIPLICATIVE_PRECEDENCE, STAR_TOKEN,
      isOperator: true, isBinaryOperator: true, isUserDefinableOperator: true);

  static const TokenType STAR_EQ = const TokenType(
      /* index = */ 69, '*=', 'STAR_EQ', ASSIGNMENT_PRECEDENCE, STAR_EQ_TOKEN,
      binaryOperatorOfCompoundAssignment: TokenType.STAR, isOperator: true);

  static const TokenType STRING_INTERPOLATION_EXPRESSION = const TokenType(
      /* index = */ 70,
      '\${',
      'STRING_INTERPOLATION_EXPRESSION',
      NO_PRECEDENCE,
      STRING_INTERPOLATION_TOKEN);

  static const TokenType STRING_INTERPOLATION_IDENTIFIER = const TokenType(
      /* index = */ 71,
      '\$',
      'STRING_INTERPOLATION_IDENTIFIER',
      NO_PRECEDENCE,
      STRING_INTERPOLATION_IDENTIFIER_TOKEN);

  static const TokenType TILDE = const TokenType(
      /* index = */ 72, '~', 'TILDE', PREFIX_PRECEDENCE, TILDE_TOKEN,
      isOperator: true, isUserDefinableOperator: true);

  static const TokenType TILDE_SLASH = const TokenType(/* index = */ 73, '~/',
      'TILDE_SLASH', MULTIPLICATIVE_PRECEDENCE, TILDE_SLASH_TOKEN,
      isOperator: true, isBinaryOperator: true, isUserDefinableOperator: true);

  static const TokenType TILDE_SLASH_EQ = const TokenType(/* index = */ 74,
      '~/=', 'TILDE_SLASH_EQ', ASSIGNMENT_PRECEDENCE, TILDE_SLASH_EQ_TOKEN,
      binaryOperatorOfCompoundAssignment: TokenType.TILDE_SLASH,
      isOperator: true);

  static const TokenType BACKPING = const TokenType(
      /* index = */ 75, '`', 'BACKPING', NO_PRECEDENCE, BACKPING_TOKEN);

  static const TokenType BACKSLASH = const TokenType(
      /* index = */ 76, '\\', 'BACKSLASH', NO_PRECEDENCE, BACKSLASH_TOKEN);

  static const TokenType PERIOD_PERIOD_PERIOD = const TokenType(
      /* index = */ 77,
      '...',
      'PERIOD_PERIOD_PERIOD',
      NO_PRECEDENCE,
      PERIOD_PERIOD_PERIOD_TOKEN);

  static const TokenType PERIOD_PERIOD_PERIOD_QUESTION = const TokenType(
      /* index = */ 78,
      '...?',
      'PERIOD_PERIOD_PERIOD_QUESTION',
      NO_PRECEDENCE,
      PERIOD_PERIOD_PERIOD_QUESTION_TOKEN);

  static const TokenType QUESTION_PERIOD_PERIOD = const TokenType(
      /* index = */ 79,
      '?..',
      'QUESTION_PERIOD_PERIOD',
      CASCADE_PRECEDENCE,
      QUESTION_PERIOD_PERIOD_TOKEN);

  static const TokenType AS = Keyword.AS;

  static const TokenType IS = Keyword.IS;

  /**
   * Token type used by error tokens.
   */
  static const TokenType BAD_INPUT = const TokenType(/* index = */ 80,
      'malformed input', 'BAD_INPUT', NO_PRECEDENCE, BAD_INPUT_TOKEN,
      stringValueShouldBeNull: true);

  /**
   * Token type used by synthetic tokens that are created during parser
   * recovery (non-analyzer use case).
   */
  static const TokenType RECOVERY = const TokenType(
      /* index = */ 81, 'recovery', 'RECOVERY', NO_PRECEDENCE, RECOVERY_TOKEN,
      stringValueShouldBeNull: true);

  // TODO(danrubel): "all" is misleading
  // because this list does not include all TokenType instances.
  static const List<TokenType> all = const <TokenType>[
    TokenType.EOF,
    TokenType.DOUBLE,
    TokenType.DOUBLE_WITH_SEPARATORS,
    TokenType.HEXADECIMAL,
    TokenType.HEXADECIMAL_WITH_SEPARATORS,
    TokenType.IDENTIFIER,
    TokenType.INT,
    TokenType.INT_WITH_SEPARATORS,
    TokenType.MULTI_LINE_COMMENT,
    TokenType.SCRIPT_TAG,
    TokenType.SINGLE_LINE_COMMENT,
    TokenType.STRING,
    TokenType.AMPERSAND,
    TokenType.AMPERSAND_AMPERSAND,
    TokenType.AMPERSAND_EQ,
    TokenType.AT,
    TokenType.BANG,
    TokenType.BANG_EQ,
    TokenType.BAR,
    TokenType.BAR_BAR,
    TokenType.BAR_EQ,
    TokenType.COLON,
    TokenType.COMMA,
    TokenType.CARET,
    TokenType.CARET_EQ,
    TokenType.CLOSE_CURLY_BRACKET,
    TokenType.CLOSE_PAREN,
    TokenType.CLOSE_SQUARE_BRACKET,
    TokenType.EQ,
    TokenType.EQ_EQ,
    TokenType.FUNCTION,
    TokenType.GT,
    TokenType.GT_EQ,
    TokenType.GT_GT,
    TokenType.GT_GT_EQ,
    TokenType.HASH,
    TokenType.INDEX,
    TokenType.INDEX_EQ,
    TokenType.LT,
    TokenType.LT_EQ,
    TokenType.LT_LT,
    TokenType.LT_LT_EQ,
    TokenType.MINUS,
    TokenType.MINUS_EQ,
    TokenType.MINUS_MINUS,
    TokenType.OPEN_CURLY_BRACKET,
    TokenType.OPEN_PAREN,
    TokenType.OPEN_SQUARE_BRACKET,
    TokenType.PERCENT,
    TokenType.PERCENT_EQ,
    TokenType.PERIOD,
    TokenType.PERIOD_PERIOD,
    TokenType.PLUS,
    TokenType.PLUS_EQ,
    TokenType.PLUS_PLUS,
    TokenType.QUESTION,
    TokenType.QUESTION_PERIOD,
    TokenType.QUESTION_QUESTION,
    TokenType.QUESTION_QUESTION_EQ,
    TokenType.SEMICOLON,
    TokenType.SLASH,
    TokenType.SLASH_EQ,
    TokenType.STAR,
    TokenType.STAR_EQ,
    TokenType.STRING_INTERPOLATION_EXPRESSION,
    TokenType.STRING_INTERPOLATION_IDENTIFIER,
    TokenType.TILDE,
    TokenType.TILDE_SLASH,
    TokenType.TILDE_SLASH_EQ,
    TokenType.BACKPING,
    TokenType.BACKSLASH,
    TokenType.PERIOD_PERIOD_PERIOD,
    TokenType.PERIOD_PERIOD_PERIOD_QUESTION,

    // TODO(danrubel): Should these be added to the "all" list?
    //TokenType.IS,
    //TokenType.AS,

    // These are not yet part of the language and not supported by the scanner:
    //TokenType.AMPERSAND_AMPERSAND_EQ,
    //TokenType.BAR_BAR_EQ,

    // Supported by the scanner but not part of the language
    //TokenType.BANG_EQ_EQ,
    //TokenType.EQ_EQ_EQ,

    // Used by synthetic tokens generated during recovery
    //TokenType.BAD_INPUT,
    //TokenType.RECOVERY,
  ];

  /**
   * A unique index identifying this [TokenType].
   */
  final int index;

  /**
   * The binary operator that is invoked by this compound assignment operator,
   * or `null` otherwise.
   */
  final TokenType? binaryOperatorOfCompoundAssignment;

  final int kind;

  /**
   * `true` if this token type represents a modifier
   * such as `abstract` or `const`.
   */
  final bool isModifier;

  /**
   * `true` if this token type represents an operator.
   */
  final bool isOperator;

  /**
   * `true` if this token type represents a binary operator.
   */
  final bool isBinaryOperator;

  /**
   * `true` if this token type represents a keyword starting a top level
   * declaration such as `class`, `enum`, `import`, etc.
   */
  final bool isTopLevelKeyword;

  /**
   * `true` if this token type represents an operator
   * that can be defined by users.
   */
  final bool isUserDefinableOperator;

  /**
   * The lexeme that defines this type of token,
   * or `null` if there is more than one possible lexeme for this type of token.
   */
  final String lexeme;

  /**
   * The name of the token type.
   */
  final String name;

  /**
   * The precedence of this type of token,
   * or `0` if the token does not represent an operator.
   */
  final int precedence;

  /**
   * See [Token.stringValue] for an explanation.
   */
  final String? stringValue;

  const TokenType(
      this.index, this.lexeme, this.name, this.precedence, this.kind,
      {this.binaryOperatorOfCompoundAssignment,
      this.isBinaryOperator = false,
      this.isModifier = false,
      this.isOperator = false,
      this.isTopLevelKeyword = false,
      this.isUserDefinableOperator = false,
      bool stringValueShouldBeNull = false})
      : this.stringValue = stringValueShouldBeNull ? null : lexeme;

  /**
   * Return `true` if this type of token represents an additive operator.
   */
  bool get isAdditiveOperator => precedence == ADDITIVE_PRECEDENCE;

  /**
   * Return `true` if this type of token represents an assignment operator.
   */
  bool get isAssignmentOperator => precedence == ASSIGNMENT_PRECEDENCE;

  /**
   * Return `true` if this type of token represents an associative operator. An
   * associative operator is an operator for which the following equality is
   * true: `(a * b) * c == a * (b * c)`. In other words, if the result of
   * applying the operator to multiple operands does not depend on the order in
   * which those applications occur.
   *
   * Note: This method considers the logical-and and logical-or operators to be
   * associative, even though the order in which the application of those
   * operators can have an effect because evaluation of the right-hand operand
   * is conditional.
   */
  bool get isAssociativeOperator =>
      this == TokenType.AMPERSAND ||
      this == TokenType.AMPERSAND_AMPERSAND ||
      this == TokenType.BAR ||
      this == TokenType.BAR_BAR ||
      this == TokenType.CARET ||
      this == TokenType.PLUS ||
      this == TokenType.STAR;

  /**
   * A flag indicating whether the keyword is a "built-in" identifier.
   */
  bool get isBuiltIn => false;

  /// A flag indicating whether the keyword is a "reserved word".
  bool get isReservedWord => false;

  /**
   * Return `true` if this type of token represents an equality operator.
   */
  bool get isEqualityOperator =>
      this == TokenType.BANG_EQ || this == TokenType.EQ_EQ;

  /**
   * Return `true` if this type of token represents an increment operator.
   */
  bool get isIncrementOperator =>
      this == TokenType.PLUS_PLUS || this == TokenType.MINUS_MINUS;

  /**
   * Return `true` if this type of token is a keyword.
   */
  bool get isKeyword => kind == KEYWORD_TOKEN;

  /**
   * A flag indicating whether the keyword can be used as an identifier
   * in some situations.
   */
  bool get isPseudo => false;

  /**
   * Return `true` if this type of token represents a multiplicative operator.
   */
  bool get isMultiplicativeOperator => precedence == MULTIPLICATIVE_PRECEDENCE;

  /**
   * Return `true` if this type of token represents a relational operator.
   */
  bool get isRelationalOperator =>
      this == TokenType.LT ||
      this == TokenType.LT_EQ ||
      this == TokenType.GT ||
      this == TokenType.GT_EQ;

  /**
   * Return `true` if this type of token represents a shift operator.
   */
  bool get isShiftOperator => precedence == SHIFT_PRECEDENCE;

  /**
   * Return `true` if this type of token represents a unary postfix operator.
   */
  bool get isUnaryPostfixOperator => precedence == POSTFIX_PRECEDENCE;

  /**
   * Return `true` if this type of token represents a unary prefix operator.
   */
  bool get isUnaryPrefixOperator =>
      precedence == PREFIX_PRECEDENCE ||
      this == TokenType.MINUS ||
      this == TokenType.PLUS_PLUS ||
      this == TokenType.MINUS_MINUS;

  /**
   * Return `true` if this type of token represents a selector operator
   * (starting token of a selector).
   */
  bool get isSelectorOperator => precedence == SELECTOR_PRECEDENCE;

  @override
  String toString() => name;
}

/// Constant list of [TokenType] and [Keyword] ordered by index.
///
/// This list should always have length 256 to avoid bounds checks in
/// SimpleToken.type:
/// DartDocTest(_tokenTypesByIndex.length, 256)
const List<TokenType> _tokenTypesByIndex = [
  TokenType.EOF,
  TokenType.DOUBLE,
  TokenType.DOUBLE_WITH_SEPARATORS,
  TokenType.HEXADECIMAL,
  TokenType.HEXADECIMAL_WITH_SEPARATORS,
  TokenType.IDENTIFIER,
  TokenType.INT,
  TokenType.INT_WITH_SEPARATORS,
  TokenType.MULTI_LINE_COMMENT,
  TokenType.SCRIPT_TAG,
  TokenType.SINGLE_LINE_COMMENT,
  TokenType.STRING,
  TokenType.AMPERSAND,
  TokenType.AMPERSAND_AMPERSAND,
  TokenType.AMPERSAND_AMPERSAND_EQ,
  TokenType.AMPERSAND_EQ,
  TokenType.AT,
  TokenType.BANG,
  TokenType.BANG_EQ,
  TokenType.BANG_EQ_EQ,
  TokenType.BAR,
  TokenType.BAR_BAR,
  TokenType.BAR_BAR_EQ,
  TokenType.BAR_EQ,
  TokenType.COLON,
  TokenType.COMMA,
  TokenType.CARET,
  TokenType.CARET_EQ,
  TokenType.CLOSE_CURLY_BRACKET,
  TokenType.CLOSE_PAREN,
  TokenType.CLOSE_SQUARE_BRACKET,
  TokenType.EQ,
  TokenType.EQ_EQ,
  TokenType.EQ_EQ_EQ,
  TokenType.FUNCTION,
  TokenType.GT,
  TokenType.GT_EQ,
  TokenType.GT_GT,
  TokenType.GT_GT_EQ,
  TokenType.GT_GT_GT,
  TokenType.GT_GT_GT_EQ,
  TokenType.HASH,
  TokenType.INDEX,
  TokenType.INDEX_EQ,
  TokenType.LT,
  TokenType.LT_EQ,
  TokenType.LT_LT,
  TokenType.LT_LT_EQ,
  TokenType.MINUS,
  TokenType.MINUS_EQ,
  TokenType.MINUS_MINUS,
  TokenType.OPEN_CURLY_BRACKET,
  TokenType.OPEN_PAREN,
  TokenType.OPEN_SQUARE_BRACKET,
  TokenType.PERCENT,
  TokenType.PERCENT_EQ,
  TokenType.PERIOD,
  TokenType.PERIOD_PERIOD,
  TokenType.PLUS,
  TokenType.PLUS_EQ,
  TokenType.PLUS_PLUS,
  TokenType.QUESTION,
  TokenType.QUESTION_PERIOD,
  TokenType.QUESTION_QUESTION,
  TokenType.QUESTION_QUESTION_EQ,
  TokenType.SEMICOLON,
  TokenType.SLASH,
  TokenType.SLASH_EQ,
  TokenType.STAR,
  TokenType.STAR_EQ,
  TokenType.STRING_INTERPOLATION_EXPRESSION,
  TokenType.STRING_INTERPOLATION_IDENTIFIER,
  TokenType.TILDE,
  TokenType.TILDE_SLASH,
  TokenType.TILDE_SLASH_EQ,
  TokenType.BACKPING,
  TokenType.BACKSLASH,
  TokenType.PERIOD_PERIOD_PERIOD,
  TokenType.PERIOD_PERIOD_PERIOD_QUESTION,
  TokenType.QUESTION_PERIOD_PERIOD,
  TokenType.BAD_INPUT,
  TokenType.RECOVERY,
  Keyword.ABSTRACT,
  Keyword.AS,
  Keyword.ASSERT,
  Keyword.ASYNC,
  Keyword.AUGMENT,
  Keyword.AWAIT,
  Keyword.BASE,
  Keyword.BREAK,
  Keyword.CASE,
  Keyword.CATCH,
  Keyword.CLASS,
  Keyword.CONST,
  Keyword.CONTINUE,
  Keyword.COVARIANT,
  Keyword.DEFAULT,
  Keyword.DEFERRED,
  Keyword.DO,
  Keyword.DYNAMIC,
  Keyword.ELSE,
  Keyword.ENUM,
  Keyword.EXPORT,
  Keyword.EXTENDS,
  Keyword.EXTENSION,
  Keyword.EXTERNAL,
  Keyword.FACTORY,
  Keyword.FALSE,
  Keyword.FINAL,
  Keyword.FINALLY,
  Keyword.FOR,
  Keyword.FUNCTION,
  Keyword.GET,
  Keyword.HIDE,
  Keyword.IF,
  Keyword.IMPLEMENTS,
  Keyword.IMPORT,
  Keyword.IN,
  Keyword.INOUT,
  Keyword.INTERFACE,
  Keyword.IS,
  Keyword.LATE,
  Keyword.LIBRARY,
  Keyword.MIXIN,
  Keyword.NATIVE,
  Keyword.NEW,
  Keyword.NULL,
  Keyword.OF,
  Keyword.ON,
  Keyword.OPERATOR,
  Keyword.OUT,
  Keyword.PART,
  Keyword.PATCH,
  Keyword.REQUIRED,
  Keyword.RETHROW,
  Keyword.RETURN,
  Keyword.SEALED,
  Keyword.SET,
  Keyword.SHOW,
  Keyword.SOURCE,
  Keyword.STATIC,
  Keyword.SUPER,
  Keyword.SWITCH,
  Keyword.SYNC,
  Keyword.THIS,
  Keyword.THROW,
  Keyword.TRUE,
  Keyword.TRY,
  Keyword.TYPEDEF,
  Keyword.VAR,
  Keyword.VOID,
  Keyword.WHEN,
  Keyword.WHILE,
  Keyword.WITH,
  Keyword.YIELD,
  // Fill to length 256 to avoid bounds check in SimpleToken.type.
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
  TokenType.UNUSED,
];

extension TokenIsAExtension on Token {
  /// Returns true if this has the token type [value].
  @pragma("vm:prefer-inline")
  bool isA(TokenType value) {
    return value.index == typeIndex;
  }
}

extension TokenTypeIsAExtension on TokenType {
  /// Returns true if this is the token type [value].
  @pragma("vm:prefer-inline")
  bool isA(TokenType value) {
    return identical(value, this);
  }
}
