///|
/// Source positions are zero-based byte offsets; line/column are one-based.
pub(all) struct Diagnostic {
  code : String
  message : String
  offset : Int32
  line : Int32
  column : Int32
} derive(Eq, Debug)

///|
/// Public AST. Repeat maximum None means unbounded; positions refer to source.
pub(all) enum Expr {
  Ref(String, Int32)
  Literal(Bytes, Bool, Int32)
  Range(Int32, Int32, Int32)
  Sequence(Array[Expr], Int32)
  Choice(Array[Expr], Int32)
  Repeat(Int32, Int32?, Expr, Int32)
  Prose(String, Int32)
} derive(Eq, Debug)

///|
pub(all) struct Rule {
  name : String
  expression : Expr
  incremental : Bool
  offset : Int32
} derive(Eq, Debug)

///|
pub(all) struct Grammar {
  rules : Array[Rule]
} derive(Eq, Debug)

///|
/// Hard implementation ceilings prevent caller budgets from risking the stack.
pub(all) struct ParseLimits {
  max_bytes : Int32
  max_rules : Int32
  max_nodes : Int32
  max_depth : Int32
} derive(Eq, Debug)

///|
pub fn default_parse_limits() -> ParseLimits {
  { max_bytes: 65536, max_rules: 256, max_nodes: 4096, max_depth: 64, }
}

///|
fn diagnostic(
  source : Bytes,
  offset : Int32,
  code : String,
  message : String,
) -> Diagnostic {
  let mut line = 1
  let mut column = 1
  for i = 0; i < offset && i < source.length(); i = i + 1 {
    if source[i] == b'\n' {
      line = line + 1
      column = 1
    } else {
      column = column + 1
    }
  }
  { code, message, offset, line, column, }
}

///|
fn Expr::offset(self : Expr) -> Int32 {
  match self {
    Ref(_, p)
    | Literal(_, _, p)
    | Range(_, _, p)
    | Sequence(_, p)
    | Choice(_, p)
    | Repeat(_, _, _, p)
    | Prose(_, p) => p
  }
}

///|
fn ascii_lower(c : Int32) -> Int32 {
  if c >= 65 && c <= 90 {
    c + 32
  } else {
    c
  }
}

///|
fn alpha(c : Int32) -> Bool {
  let v = ascii_lower(c)
  v >= 97 && v <= 122
}

///|
fn digit(c : Int32) -> Bool {
  c >= 48 && c <= 57
}

///|
fn lower_name(s : String) -> String {
  s.to_lower()
}

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

///|
pub extend Diagnostic with @debug.Debug::{to_repr}

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

///|
pub extend Expr with @debug.Debug::{to_repr}

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

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

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

///|
pub extend Grammar with @debug.Debug::{to_repr}

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

///|
pub extend ParseLimits with @debug.Debug::{to_repr}