///|
/// CSS tokens, as CSS Syntax Level 3 defines them.
///
/// The token set is the specification's, not a convenient subset, because the
/// specification's error handling is defined in terms of it: "consume the
/// remnants of a bad declaration" means "discard tokens until a `;` or a
/// matching `}`", and that rule is only implementable if `;` and `}` are
/// tokens rather than characters. A tokenizer that folded them into a generic
/// `Delim` would have to re-derive the block structure to recover.
///
/// Whitespace and comments are tokens too. The parser skips them, but they have
/// to exist: whitespace is the descendant combinator, so a selector parser that
/// could not see it would be unable to tell `a b` from `ab`.

///|
/// Whether a `#...` can be an id selector, or only a hex colour.
///
/// The distinction is the specification's and it is load-bearing: `#1a2b3c` is
/// a colour but not a valid id, so `#foo` and `#1a2b3c` cannot be the same
/// token if a selector parser is to reject the second.
pub(all) enum HashKind {
  Id
  Unrestricted
} derive(Eq, Debug)

///|
pub(all) enum TokenKind {
  Ident(String)
  /// An ident immediately followed by `(`. The `(` is part of this token, so a
  /// function call can never be confused with an ident beside a group.
  Function(String)
  AtKeyword(String)
  Hash(String, HashKind)
  Str(String)
  /// A string that hit a newline or the end of input.
  BadStr
  /// A `url(...)` with an unquoted body. A quoted one is `Function("url")`
  /// followed by a `Str`, exactly as the specification has it.
  Url(String)
  BadUrl
  /// A number, with its source spelling kept.
  Number(String, Double, Bool)
  Percentage(String, Double)
  Dimension(String, Double, Bool, String)
  Whitespace
  Colon
  Semicolon
  Comma
  LBracket
  RBracket
  LParen
  RParen
  LBrace
  RBrace
  /// ``, which are legal at the top level of a stylesheet.
  Cdo
  Cdc
  /// The body, without the `/*` and `*/`.
  Comment(String)
  /// Any other single character.
  Delim(Char)
  /// A real variant rather than an `Option`, so that no lookahead has to
  /// unwrap and every parser loop has a terminating case to match.
  Eof
} derive(Eq, Debug)

///|
pub(all) struct Token {
  kind : TokenKind
  span : @span.Span
} derive(Eq, Debug)

///|
/// Whether this token is skipped when looking for the next meaningful one.
pub fn Token::is_trivia(self : Token) -> Bool {
  match self.kind {
    Whitespace | Comment(_) => true
    _ => false
  }
}

///|
/// The opener that this closer matches, if it is one.
///
/// Used by error recovery, which has to know how deep it is before it can know
/// whether a `}` ends the thing it is skipping.
pub fn TokenKind::closer_for(self : TokenKind) -> TokenKind? {
  match self {
    RParen => Some(LParen)
    RBracket => Some(LBracket)
    RBrace => Some(LBrace)
    _ => None
  }
}

///|
pub fn TokenKind::is_opener(self : TokenKind) -> Bool {
  match self {
    LParen | LBracket | LBrace | Function(_) => true
    _ => false
  }
}

///|
/// A stable one-line rendering of a token, for tests and for the oracle.
///
/// Hand-written rather than derived because this string is an interface: the
/// differential harness compares it, so it must not change when a variant is
/// renamed or a field reordered. It is also the only place a `Char` or a
/// `Double` gets formatted, which keeps backend differences in number printing
/// confined to one function.
pub fn TokenKind::to_debug(self : TokenKind) -> String {
  match self {
    Ident(s) => "Ident(" + q(s) + ")"
    Function(s) => "Function(" + q(s) + ")"
    AtKeyword(s) => "AtKeyword(" + q(s) + ")"
    Hash(s, k) =>
      "Hash(" +
      q(s) +
      ", " +
      (match k {
        Id => "Id"
        Unrestricted => "Unrestricted"
      }) +
      ")"
    Str(s) => "Str(" + q(s) + ")"
    BadStr => "BadStr"
    Url(s) => "Url(" + q(s) + ")"
    BadUrl => "BadUrl"
    Number(r, _, i) =>
      "Number(" + q(r) + ", " + (if i { "int" } else { "num" }) + ")"
    Percentage(r, _) => "Percentage(" + q(r) + ")"
    Dimension(r, _, _, u) => "Dimension(" + q(r) + ", " + q(u) + ")"
    Whitespace => "Whitespace"
    Colon => "Colon"
    Semicolon => "Semicolon"
    Comma => "Comma"
    LBracket => "LBracket"
    RBracket => "RBracket"
    LParen => "LParen"
    RParen => "RParen"
    LBrace => "LBrace"
    RBrace => "RBrace"
    Cdo => "Cdo"
    Cdc => "Cdc"
    Comment(s) => "Comment(" + q(s) + ")"
    Delim(c) => "Delim(" + q(c.to_string()) + ")"
    Eof => "Eof"
  }
}

///|
/// A quoted, escaped string, so that a token dump is one line whatever the
/// token contained.
fn q(s : String) -> String {
  let b = StringBuilder()
  b.write_string("\"")
  for c in s {
    match c {
      '"' => b.write_string("\\\"")
      '\\' => b.write_string("\\\\")
      '\n' => b.write_string("\\n")
      '\r' => b.write_string("\\r")
      '\t' => b.write_string("\\t")
      _ => b.write_char(c)
    }
  }
  b.write_string("\"")
  b.to_string()
}

///|
/// A whole token stream, one token per line, for the differential harness.
pub fn dump(toks : Array[Token]) -> String {
  let b = StringBuilder()
  for t in toks {
    b.write_string(t.span.start.to_string())
    b.write_string("..")
    b.write_string(t.span.end.to_string())
    b.write_string(" ")
    b.write_string(t.kind.to_debug())
    b.write_string("\n")
  }
  b.to_string()
}