///|
/// The reserved words of Gleam v1.19.2 (`string_to_keyword` in the
/// compiler's lexer), in alphabetical order.
pub(all) enum Keyword {
  As
  Assert
  Auto
  Case
  Const
  Delegate
  Derive
  Echo
  Else
  Fn
  If
  Implement
  Import
  Let
  Macro
  Opaque
  Panic
  Pub
  Test
  Todo
  Type
  Use
} derive(Eq, Debug, Hash)

///|
/// The keyword for `word`, or `None` when `word` is not reserved.
pub fn Keyword::of(word : String) -> Keyword? {
  match word {
    "as" => Some(As)
    "assert" => Some(Assert)
    "auto" => Some(Auto)
    "case" => Some(Case)
    "const" => Some(Const)
    "delegate" => Some(Delegate)
    "derive" => Some(Derive)
    "echo" => Some(Echo)
    "else" => Some(Else)
    "fn" => Some(Fn)
    "if" => Some(If)
    "implement" => Some(Implement)
    "import" => Some(Import)
    "let" => Some(Let)
    "macro" => Some(Macro)
    "opaque" => Some(Opaque)
    "panic" => Some(Panic)
    "pub" => Some(Pub)
    "test" => Some(Test)
    "todo" => Some(Todo)
    "type" => Some(Type)
    "use" => Some(Use)
    _ => None
  }
}

///|
/// The source text of the keyword.
pub fn Keyword::text(self : Keyword) -> String {
  match self {
    As => "as"
    Assert => "assert"
    Auto => "auto"
    Case => "case"
    Const => "const"
    Delegate => "delegate"
    Derive => "derive"
    Echo => "echo"
    Else => "else"
    Fn => "fn"
    If => "if"
    Implement => "implement"
    Import => "import"
    Let => "let"
    Macro => "macro"
    Opaque => "opaque"
    Panic => "panic"
    Pub => "pub"
    Test => "test"
    Todo => "todo"
    Type => "type"
    Use => "use"
  }
}

///|
/// The token kinds of the compiler's `Token`, without the four trivia kinds
/// (`CommentNormal`, `CommentDoc`, `CommentModule`, `NewLine`), which are
/// `Trivia` here, and without `EndOfFile`. `Error` is the text that a
/// lexical error covers; the compiler stops there, this scanner goes on.
pub(all) enum TokenKind {
  Name(String)
  UpName(String)
  DiscardName(String)
  /// `value` is the lexeme with its sign, radix prefix and `_` separators;
  /// `int_value` is the number.
  Int(value~ : String, int_value~ : BigInt)
  /// `value` is the lexeme (with `_` separators); `float_value` the number,
  /// infinity when the literal is too big.
  Float(value~ : String, float_value~ : Double)
  /// The text between the quotes, escapes kept as written, `\r\n` and lone
  /// `\r` replaced by `\n` (as the compiler does).
  String(String)
  LeftParen
  RightParen
  LeftSquare
  RightSquare
  LeftBrace
  RightBrace
  Plus
  Minus
  Star
  Slash
  Less
  Greater
  LessEqual
  GreaterEqual
  Percent
  PlusDot
  MinusDot
  StarDot
  SlashDot
  LessDot
  GreaterDot
  LessEqualDot
  GreaterEqualDot
  Concatenate
  Colon
  Comma
  Hash
  Bang
  Equal
  EqualEqual
  NotEqual
  Vbar
  VbarVbar
  AmperAmper
  LtLt
  GtGt
  Pipe
  Dot
  RArrow
  LArrow
  DotDot
  At
  Keyword(Keyword)
  Error
} derive(Eq, Debug)

///|
/// The three comment forms: `//`, `///` (documentation) and `////`
/// (module documentation).
pub(all) enum CommentKind {
  Regular
  Doc
  Module
} derive(Eq, Debug, Hash)

///|
/// The number of slashes that start the comment.
pub fn CommentKind::prefix_length(self : CommentKind) -> Int32 {
  match self {
    Regular => 2
    Doc => 3
    Module => 4
  }
}

///|
/// A comment. `span` covers the slashes and the text up to the line break
/// (not included); `content` is the text after the slashes.
pub(all) struct Comment {
  kind : CommentKind
  content : String
  span : @span.SrcSpan
} derive(Eq, Debug)

///|
/// The compiler's span for the comment: its content, without the slashes.
pub fn Comment::content_span(self : Comment) -> @span.SrcSpan {
  @span.SrcSpan::new(self.span.start + self.kind.prefix_length(), self.span.end)
}

///|
/// Text between tokens. `Whitespace` is a run of spaces, tabs and form
/// feeds, with the byte order mark when the text starts with one (a byte
/// order mark anywhere else is an unknown character, as in the compiler);
/// `Newline` is one `\n`, `\r\n` or `\r`; a `Comment` runs to the end of its
/// line.
pub(all) enum Trivia {
  Whitespace(String, @span.SrcSpan)
  Newline(String, @span.SrcSpan)
  Comment(Comment)
} derive(Eq, Debug)

///|
/// The source text of the trivia.
pub fn Trivia::text(self : Trivia) -> String {
  match self {
    Whitespace(text, _) | Newline(text, _) => text
    Comment(c) => {
      let slashes = match c.kind {
        Regular => "//"
        Doc => "///"
        Module => "////"
      }
      slashes + c.content
    }
  }
}

///|
pub fn Trivia::span(self : Trivia) -> @span.SrcSpan {
  match self {
    Whitespace(_, span) | Newline(_, span) => span
    Comment(c) => c.span
  }
}

///|
/// A token with the trivia around it. All trivia before a token is its
/// `trivia_before`; `trivia_after` is empty except for the last token of a
/// stream, which holds the trivia to the end of the text.
pub(all) struct Token {
  kind : TokenKind
  lexeme : String
  span : @span.SrcSpan
  trivia_before : ArrayView[Trivia]
  trivia_after : ArrayView[Trivia]
} derive(Eq, Debug)

///|
/// `trivia_before`, the lexeme and `trivia_after`, as in the source.
pub fn Token::to_source(self : Token) -> String {
  let sb = StringBuilder()
  for t in self.trivia_before {
    sb.write_string(t.text())
  }
  sb.write_string(self.lexeme)
  for t in self.trivia_after {
    sb.write_string(t.text())
  }
  sb.to_string()
}

///|
/// The result of a scan. `trivia` is the trivia of a text with no tokens
/// and empty otherwise. `diagnostics` are the lexical errors in source
/// order; each has one `Error` token in `tokens`.
pub(all) struct TokenStream {
  tokens : ArrayView[Token]
  trivia : ArrayView[Trivia]
  diagnostics : ArrayView[@diagnostic.Diagnostic]
} derive(Eq, Debug)

///|
/// The scanned text, byte for byte.
pub fn TokenStream::to_source(self : TokenStream) -> String {
  let sb = StringBuilder()
  for t in self.trivia {
    sb.write_string(t.text())
  }
  for token in self.tokens {
    sb.write_string(token.to_source())
  }
  sb.to_string()
}

///|
/// Whether any diagnostic has severity `Error`.
pub fn TokenStream::has_errors(self : TokenStream) -> Bool {
  self.diagnostics.iter().any(d => d.is_error())
}