///|
priv struct Parser {
  tokens : Array[Token]
  mut i : Int32
  prefixes : Map[String, String]
  limits : Limits
  mut depth : Int32
  mut template_mode : Bool
}

///|
fn Parser::new(source : String, limits : Limits) -> Parser raise QueryError {
  {
    tokens: lex(source, limits),
    i: 0,
    prefixes: Map([]),
    limits,
    depth: 0,
    template_mode: false,
  }
}

///|
fn Parser::current(self : Parser) -> Lexeme {
  self.tokens[self.i].value
}

///|
fn Parser::set_template_mode(self : Parser, enabled : Bool) -> Unit {
  self.template_mode = enabled
}

///|
fn Parser::position(self : Parser) -> Int32 {
  self.tokens[self.i].position
}

///|
fn Parser::advance(self : Parser) -> Lexeme {
  let value = self.current()
  if value != EndToken {
    self.i += 1
  }
  value
}

///|
fn Parser::sym(self : Parser, symbol : String) -> Bool {
  if self.current() == Symbol(symbol) {
    self.i += 1
    true
  } else {
    false
  }
}

///|
fn Parser::word(self : Parser, wanted : String) -> Bool {
  match self.current() {
    Word(w) =>
      if w.to_upper() == wanted {
        self.i += 1
        true
      } else {
        false
      }
    _ => false
  }
}

///|
fn Parser::expect(self : Parser, symbol : String) -> Unit raise QueryError {
  if !self.sym(symbol) {
    raise Syntax(self.position(), "expected " + symbol)
  }
}

///|
fn Parser::require_word(self : Parser, word : String) -> Unit raise QueryError {
  if !self.word(word) {
    raise Syntax(self.position(), "expected " + word)
  }
}

///|
fn Parser::variable(self : Parser) -> String raise QueryError {
  match self.advance() {
    Var(name) => name
    _ => raise Syntax(self.position(), "expected variable")
  }
}

///|
fn Parser::enter(self : Parser) -> Unit raise QueryError {
  if self.depth >= self.limits.max_depth {
    raise Resource("query nesting")
  }
  self.depth += 1
}

///|
fn Parser::prefixes(self : Parser) -> Unit raise QueryError {
  while self.word("PREFIX") {
    let prefix = match self.advance() {
      Word(w) =>
        match w.split_once(":") {
          Some((prefix, tail)) =>
            if tail.length() == 0 {
              prefix.to_owned()
            } else {
              raise Syntax(self.position(), "prefix must end with colon")
            }
          None => raise Syntax(self.position(), "expected prefix name")
        }
      _ => raise Syntax(self.position(), "expected prefix name")
    }
    let base = match self.advance() {
      IriToken(v) => {
        check_iri(v)
        v
      }
      _ => raise Syntax(self.position(), "expected prefix IRI")
    }
    self.prefixes[prefix] = base
  }
  if self.word("BASE") {
    raise Unsupported(self.position(), "BASE and relative IRIs")
  }
}

///|
fn Parser::read_term(self : Parser) -> Term raise QueryError {
  let position = self.position()
  match self.advance() {
    IriToken(v) => iri(v)
    StringToken(v) =>
      if self.sym("@") {
        let lang = match self.advance() {
          Word(l) => l
          _ => raise Syntax(self.position(), "expected language tag")
        }
        language_literal(v, lang)
      } else if self.sym("^^") {
        let datatype = self.read_term()
        if datatype.kind != IriKind {
          raise Syntax(position, "datatype must be IRI")
        }
        typed_literal(v, datatype.lexical)
      } else {
        literal(v)
      }
    NumToken(n) => {
      if n.length() > self.limits.max_numeric_digits {
        raise Resource("numeric token digits")
      }
      let kind = if n.contains("e") || n.contains("E") {
        "double"
      } else if n.contains(".") {
        "decimal"
      } else {
        "integer"
      }
      typed_literal(n, xsd_namespace() + kind)
    }
    Symbol(sign) if sign == "+" || sign == "-" => {
      let value = self.read_term()
      if value.datatype != xsd_namespace() + "integer" &&
        value.datatype != xsd_namespace() + "decimal" &&
        value.datatype != xsd_namespace() + "double" {
        raise Syntax(position, "sign requires numeric literal")
      }
      typed_literal(sign + value.lexical, value.datatype)
    }
    Word(w) => {
      if w.to_lower() == "true" || w.to_lower() == "false" {
        return typed_literal(w.to_lower(), xsd_namespace() + "boolean")
      }
      match w.split_once(":") {
        Some((prefix, suffix)) =>
          if prefix == "_" {
            if !self.template_mode {
              raise Unsupported(
                position, "blank node query labels; use variables",
              )
            }
            blank(suffix.to_owned())
          } else {
            let base = match self.prefixes.get(prefix.to_owned()) {
              Some(v) => v
              None =>
                raise Syntax(position, "unknown prefix " + prefix.to_owned())
            }
            iri(base + suffix.to_owned())
          }
        None => raise Syntax(position, "expected RDF term")
      }
    }
    _ => raise Syntax(position, "expected RDF term")
  }
}

///|
fn Parser::slot(self : Parser) -> Slot raise QueryError {
  match self.current() {
    Var(_) => Variable(self.variable())
    _ => Fixed(self.read_term())
  }
}

///|
fn Parser::path_atom(self : Parser) -> Path raise QueryError {
  self.enter()
  let mut path = if self.sym("^") {
    Inverse(self.path_atom())
  } else if self.sym("(") {
    let inner = self.read_path()
    self.expect(")")
    inner
  } else if self.word("A") {
    Predicate(iri(rdf_namespace() + "type"))
  } else {
    match self.current() {
      Var(_) => PathVar(self.variable())
      _ => {
        let term = self.read_term()
        if term.kind != IriKind {
          raise Syntax(self.position(), "predicate must be IRI")
        }
        Predicate(term)
      }
    }
  }
  if self.sym("*") {
    path = ZeroMore(path)
  } else if self.sym("+") {
    path = OneMore(path)
  } else if self.sym("?") {
    path = ZeroOne(path)
  }
  self.depth -= 1
  path
}

///|
fn Parser::path_sequence(self : Parser) -> Path raise QueryError {
  let mut path = self.path_atom()
  while self.sym("/") {
    path = Sequence(path, self.path_atom())
  }
  path
}

///|
fn Parser::read_path(self : Parser) -> Path raise QueryError {
  let mut path = self.path_sequence()
  while self.sym("|") {
    path = Alternative(path, self.path_sequence())
  }
  path
}