///|
/// An input or budget failure. Results never silently report budget exhaustion as rejection.
pub(all) enum Problem {
  InvalidGrammar(String)
  InvalidToken(Int32, String)
  Limit(String, Int32)
  InvalidNode(Int32)
  InvalidLimit(String)
  Syntax(Int32, String)
} derive(Eq, Debug)

///|
/// CFG symbols are names, never regular expressions or literal text matchers.
pub(all) enum Symbol {
  Terminal(String)
  Nonterminal(String)
} derive(Eq, Debug)

///|
/// Rule labels identify alternatives independently of their symbol sequences.
pub(all) struct Rule {
  lhs : String
  rhs : Array[Symbol]
  label : String
} derive(Eq, Debug)

///|
/// Span units belong to the caller (typically UTF-16 or byte offsets).
pub(all) struct Token {
  kind : String
  text : String
  start : Int32
  end : Int32
} derive(Eq, Debug)

///|
/// Snapshot a token, preventing accidental dependence on caller arrays.
pub fn token(kind : String, text : String, start : Int32, end : Int32) -> Token {
  { kind, text, start, end, }
}

///|
/// Validate monotonic nonoverlapping spans; zero-width tokens are permitted.
pub fn validate_tokens(tokens : Array[Token]) -> Result[Unit, Problem] {
  let mut previous = 0
  for i, t in tokens {
    if t.kind == "" {
      return Err(InvalidToken(i, "empty token kind"))
    }
    if t.start < previous || t.end < t.start {
      return Err(InvalidToken(i, "negative, reversed or overlapping span"))
    }
    previous = t.end
  }
  Ok(())
}

///|
pub extend Problem with Eq::{equal, not_equal}

///|
pub extend Problem with @moonbitlang/core/debug.Debug::{to_repr}

///|
pub extend Symbol with Eq::{equal, not_equal}

///|
pub extend Symbol with @moonbitlang/core/debug.Debug::{to_repr}

///|
pub extend Rule with Eq::{equal, not_equal}

///|
pub extend Rule with @moonbitlang/core/debug.Debug::{to_repr}

///|
pub extend Token with Eq::{equal, not_equal}

///|
pub extend Token with @moonbitlang/core/debug.Debug::{to_repr}