///|
pub struct Parser {
  tokens : Array[Token]
  mut cursor : Int
  schema : Schema
}

///|
pub fn Parser::new(
  source : String,
  limits? : Limits = Limits::default(),
) -> Parser raise XdrError {
  {
    tokens: tokenize(source, limits~),
    cursor: 0,
    schema: Schema::empty(limits~),
  }
}

///|
pub fn Parser::peek(self : Parser) -> String {
  self.tokens[self.cursor].text
}

///|
pub fn Parser::offset(self : Parser) -> Int {
  self.tokens[self.cursor].offset
}

///|
pub fn Parser::take(self : Parser) -> String raise XdrError {
  let s = self.peek()
  if s == "" {
    raise Invalid(self.offset(), "unexpected end of schema")
  }
  self.cursor += 1
  s
}

///|
pub fn Parser::expect(self : Parser, expected : String) -> Unit raise XdrError {
  if self.peek() != expected {
    raise Invalid(self.offset(), "expected " + expected)
  }
  ignore(self.take())
}

///|
pub fn Parser::identifier(self : Parser) -> String raise XdrError {
  let s = self.peek()
  if s == "" || !ident_start(s.to_array()[0]) {
    raise Invalid(self.offset(), "expected identifier")
  }
  match s {
    "const"
    | "typedef"
    | "enum"
    | "struct"
    | "union"
    | "switch"
    | "case"
    | "default"
    | "unsigned"
    | "int"
    | "hyper"
    | "float"
    | "double"
    | "quadruple"
    | "bool"
    | "opaque"
    | "string"
    | "void" => raise Invalid(self.offset(), "reserved identifier")
    _ => self.take()
  }
}

///|
pub fn Parser::value(self : Parser) -> Int raise XdrError {
  let offset = self.offset()
  let s = self.take()
  match self.schema.constants.get(s) {
    Some(n) => n
    None =>
      if s == "TRUE" {
        1
      } else if s == "FALSE" {
        0
      } else {
        parse_constant_number(s, offset~)
      }
  }
}

///|
pub fn Parser::bound(self : Parser, closing : String) -> Int raise XdrError {
  let n = if self.peek() == closing {
    self.schema.limits.max_items
  } else {
    self.value()
  }
  if n < 0 {
    raise Invalid(self.offset(), "negative bound")
  }
  self.expect(closing)
  n
}

///|
pub fn Parser::base_type(self : Parser, depth : Int) -> Type raise XdrError {
  if depth > self.schema.limits.max_depth {
    raise Invalid(self.offset(), "schema nesting limit")
  }
  match self.take() {
    "void" => TVoid
    "enum" => self.compound("enum", depth + 1)
    "struct" => self.compound("struct", depth + 1)
    "union" => self.compound("union", depth + 1)
    "int" => TInt
    "hyper" => THyper
    "bool" => TBool
    "float" => TFloat
    "double" => TDouble
    "unsigned" =>
      match self.take() {
        "int" => TUInt
        "hyper" => TUHyper
        _ => raise Invalid(self.offset(), "unsigned requires int or hyper")
      }
    name => {
      if name == "" || !ident_start(name.to_array()[0]) {
        raise Invalid(self.offset(), "expected type")
      }
      TRef(name)
    }
  }
}

///|
pub fn Parser::declaration(self : Parser, depth : Int) -> Field raise XdrError {
  if self.peek() == "opaque" || self.peek() == "string" {
    let kind = self.take()
    let name = self.identifier()
    if self.peek() == "[" && kind == "opaque" {
      self.expect("[")
      let n = self.bound("]")
      return { name, ty: TOpaque(n, false), }
    }
    self.expect("<")
    let n = self.bound(">")
    return {
      name,
      ty: if kind == "string" {
        TString(n)
      } else {
        TOpaque(n, true)
      },
    }
  }
  let base = self.base_type(depth)
  let pointer = self.peek() == "*"
  if pointer {
    self.expect("*")
  }
  let name = self.identifier()
  let ty = if pointer {
    TOptional(base)
  } else if self.peek() == "[" {
    self.expect("[")
    TArray(base, self.bound("]"), false)
  } else if self.peek() == "<" {
    self.expect("<")
    TArray(base, self.bound(">"), true)
  } else {
    base
  }
  { name, ty, }
}

///|
pub fn Parser::add_type(
  self : Parser,
  name : String,
  ty : Type,
) -> Unit raise XdrError {
  if self.schema.types.contains(name) || self.schema.constants.contains(name) {
    raise Invalid(self.offset(), "duplicate symbol: " + name)
  }
  self.schema.types[name] = ty
}

///|
pub fn Parser::add_constant(
  self : Parser,
  name : String,
  value : Int,
) -> Unit raise XdrError {
  if self.schema.types.contains(name) || self.schema.constants.contains(name) {
    raise Invalid(self.offset(), "duplicate symbol: " + name)
  }
  self.schema.constants[name] = value
}

///|
pub fn parse_schema(
  source : String,
  limits? : Limits = Limits::default(),
) -> Schema raise XdrError {
  let p = Parser::new(source, limits~)
  while p.peek() != "" {
    match p.take() {
      "const" => {
        let name = p.identifier()
        p.expect("=")
        let n = p.value()
        p.expect(";")
        p.add_constant(name, n)
      }
      "typedef" => {
        let f = p.declaration(0)
        p.expect(";")
        p.add_type(f.name, f.ty)
      }
      "enum" | "struct" | "union" as kind => {
        let name = p.identifier()
        let ty = p.compound(kind, 0)
        p.expect(";")
        p.add_type(name, ty)
      }
      _ => raise Invalid(p.offset(), "expected definition")
    }
  }
  p.schema.validate()
  p.schema
}