///|
pub(all) struct Span {
  start : Int
  end : Int
  line : Int
  column : Int
} derive(Eq, Debug)

///|
pub(all) struct Diagnostic {
  code : String
  message : String
  span : Span
} derive(Eq, Debug)

///|
pub(all) suberror SieveError {
  SieveError(Diagnostic)
} derive(Debug)

///|
pub(all) struct Limits {
  source_chars : Int
  tokens : Int
  string_chars : Int
  depth : Int
  steps : Int
  comparisons : Int
  actions : Int
  headers : Int
  variables : Int
} derive(Eq, Debug)

///|
pub fn Limits::default() -> Limits {
  {
    source_chars: 262144,
    tokens: 32768,
    string_chars: 65536,
    depth: 64,
    steps: 100000,
    comparisons: 2000000,
    actions: 128,
    headers: 1000,
    variables: 128,
  }
}

///|
pub fn Limits::check(self : Limits) -> Unit raise SieveError {
  if self.source_chars < 1 ||
    self.source_chars > 4194304 ||
    self.tokens < 1 ||
    self.tokens > 262144 ||
    self.string_chars < 1 ||
    self.string_chars > self.source_chars ||
    self.depth < 1 ||
    self.depth > 128 ||
    self.steps < 1 ||
    self.steps > 10000000 ||
    self.comparisons < 1 ||
    self.comparisons > 100000000 ||
    self.actions < 1 ||
    self.actions > 4096 ||
    self.headers < 1 ||
    self.headers > 100000 ||
    self.variables < 1 ||
    self.variables > 4096 {
    fail(
      "limit.invalid",
      "limits must be positive and within safe ceilings",
      origin(),
    )
  }
}

///|
fn origin() -> Span {
  { start: 0, end: 0, line: 1, column: 1 }
}

///|
fn fail(code : String, message : String, span : Span) -> Unit raise SieveError {
  raise SieveError({ code, message, span })
}

///|
fn ascii_lower(text : String) -> String {
  let b = StringBuilder()
  for c in text {
    b.write_char(c.to_ascii_lowercase())
  }
  b.to_string()
}

///|
fn chars_text(chars : Array[Char], start : Int, end : Int) -> String {
  let b = StringBuilder()
  for i = start; i < end; i = i + 1 {
    b.write_char(chars[i])
  }
  b.to_string()
}