///|
/// Parse a complete GraphQL SDL document.
///
/// The parser accepts schema definitions, type definitions, type extensions, and
/// directive definitions, preserving descriptions and source positions in the
/// returned AST.
pub fn parse_schema(source : String) -> SchemaDocument raise ParseError {
  let parser = Parser::new(source)
  let definitions : Array[SchemaDefinition] = []
  if parser.at_eof() {
    parse_error("expected schema definition", parser.peek().position)
  }
  while true {
    if parser.at_eof() {
      break
    }
    definitions.push(parser.parse_schema_definition())
    continue
  }
  parser.expect_eof()
  { definitions, }
}

///|
fn Parser::parse_schema_definition(
  self : Parser,
) -> SchemaDefinition raise ParseError {
  if self.check_ident("schema") {
    SchemaDef(self.parse_schema_root_definition())
  } else if self.check_ident("extend") {
    TypeExt(self.parse_type_extension())
  } else {
    self.parse_described_definition()
  }
}

///|
fn Parser::parse_schema_root_definition(
  self : Parser,
) -> SchemaDefinitionData raise ParseError {
  let position = self.expect_ident("schema").position
  let directives = self.parse_directives()
  ignore(self.expect_punct("{"))
  let mut query : String? = None
  let mut mutation : String? = None
  let mut subscription : String? = None
  while true {
    if self.check_punct("}") {
      ignore(self.take())
      break
    }
    let (operation, op_pos) = self.expect_name()
    ignore(self.expect_punct(":"))
    let (type_name, _) = self.expect_name()
    match operation {
      "query" =>
        match query {
          Some(_) => parse_error("duplicate `query` operation", op_pos)
          None => query = Some(type_name)
        }
      "mutation" =>
        match mutation {
          Some(_) => parse_error("duplicate `mutation` operation", op_pos)
          None => mutation = Some(type_name)
        }
      "subscription" =>
        match subscription {
          Some(_) => parse_error("duplicate `subscription` operation", op_pos)
          None => subscription = Some(type_name)
        }
      _ => parse_error("expected query, mutation or subscription", op_pos)
    }
    continue
  }
  { position, directives, query, mutation, subscription, }
}

///|
fn Parser::parse_described_definition(
  self : Parser,
) -> SchemaDefinition raise ParseError {
  let description = if self.peek().kind == TkStringValue ||
    self.peek().kind == TkBlockString {
    Some(self.parse_string_literal())
  } else {
    None
  }
  if self.check_ident("scalar") {
    TypeDef(ScalarTypeDef(self.parse_scalar_type(description)))
  } else if self.check_ident("type") {
    TypeDef(ObjectTypeDef(self.parse_object_type(description)))
  } else if self.check_ident("interface") {
    TypeDef(InterfaceTypeDef(self.parse_interface_type(description)))
  } else if self.check_ident("union") {
    TypeDef(UnionTypeDef(self.parse_union_type(description)))
  } else if self.check_ident("enum") {
    TypeDef(EnumTypeDef(self.parse_enum_type(description)))
  } else if self.check_ident("input") {
    TypeDef(InputObjectTypeDef(self.parse_input_object_type(description)))
  } else if self.check_ident("directive") {
    DirectiveDef(self.parse_directive_definition(description))
  } else {
    let token = self.peek()
    parse_error("expected type or directive definition", token.position)
  }
}

///|
fn Parser::parse_type_extension(
  self : Parser,
) -> TypeExtension raise ParseError {
  ignore(self.expect_ident("extend"))
  if self.check_ident("scalar") {
    ScalarTypeExt(self.parse_scalar_type_extension())
  } else if self.check_ident("type") {
    ObjectTypeExt(self.parse_object_type_extension())
  } else if self.check_ident("interface") {
    InterfaceTypeExt(self.parse_interface_type_extension())
  } else if self.check_ident("union") {
    UnionTypeExt(self.parse_union_type_extension())
  } else if self.check_ident("enum") {
    EnumTypeExt(self.parse_enum_type_extension())
  } else if self.check_ident("input") {
    InputObjectTypeExt(self.parse_input_object_type_extension())
  } else {
    let token = self.peek()
    parse_error("expected type extension", token.position)
  }
}

///|
fn Parser::parse_scalar_type(
  self : Parser,
  description : String?,
) -> ScalarType raise ParseError {
  let position = self.expect_ident("scalar").position
  let (name, _) = self.expect_name()
  let directives = self.parse_directives()
  { position, description, name, directives, }
}

///|
fn Parser::parse_scalar_type_extension(
  self : Parser,
) -> ScalarTypeExtension raise ParseError {
  let position = self.expect_ident("scalar").position
  let (name, _) = self.expect_name()
  let directives = self.parse_directives()
  if directives.is_empty() {
    parse_error(
      "Scalar type extension should contain at least one directive.", position,
    )
  }
  { position, name, directives, }
}

///|
fn Parser::parse_implements_interfaces(
  self : Parser,
) -> Array[String] raise ParseError {
  if !self.eat_ident("implements") {
    return []
  }
  ignore(self.eat_punct("&"))
  let interfaces : Array[String] = []
  let (name, _) = self.expect_name()
  interfaces.push(name)
  while true {
    if !self.eat_punct("&") {
      break
    }
    let (name, _) = self.expect_name()
    interfaces.push(name)
    continue
  }
  interfaces
}

///|
fn Parser::parse_input_value(self : Parser) -> InputValue raise ParseError {
  let position = self.peek().position
  let description = if self.peek().kind == TkStringValue ||
    self.peek().kind == TkBlockString {
    Some(self.parse_string_literal())
  } else {
    None
  }
  let (name, _) = self.expect_name()
  ignore(self.expect_punct(":"))
  let value_type = self.parse_type()
  let default_value = if self.eat_punct("=") {
    Some(self.parse_default_value())
  } else {
    None
  }
  let directives = self.parse_directives()
  { position, description, name, value_type, default_value, directives, }
}

///|
fn Parser::parse_arguments_definition(
  self : Parser,
) -> Array[InputValue] raise ParseError {
  if !self.eat_punct("(") {
    return []
  }
  let args : Array[InputValue] = []
  if self.check_punct(")") {
    let token = self.peek()
    parse_error("expected argument definition", token.position)
  }
  while true {
    if self.check_punct(")") {
      ignore(self.take())
      break
    }
    args.push(self.parse_input_value())
    continue
  }
  args
}

///|
fn Parser::parse_schema_field(self : Parser) -> SchemaField raise ParseError {
  let position = self.peek().position
  let description = if self.peek().kind == TkStringValue ||
    self.peek().kind == TkBlockString {
    Some(self.parse_string_literal())
  } else {
    None
  }
  let (name, _) = self.expect_name()
  let arguments = self.parse_arguments_definition()
  ignore(self.expect_punct(":"))
  let field_type = self.parse_type()
  let directives = self.parse_directives()
  { position, description, name, arguments, field_type, directives, }
}

///|
fn Parser::parse_fields(self : Parser) -> Array[SchemaField] raise ParseError {
  if !self.eat_punct("{") {
    return []
  }
  let fields : Array[SchemaField] = []
  if self.check_punct("}") {
    let token = self.peek()
    parse_error("expected field", token.position)
  }
  while true {
    if self.check_punct("}") {
      ignore(self.take())
      break
    }
    fields.push(self.parse_schema_field())
    continue
  }
  fields
}

///|
fn Parser::parse_object_type(
  self : Parser,
  description : String?,
) -> ObjectType raise ParseError {
  let position = self.expect_ident("type").position
  let (name, _) = self.expect_name()
  let implements_interfaces = self.parse_implements_interfaces()
  let directives = self.parse_directives()
  let fields = self.parse_fields()
  { position, description, name, implements_interfaces, directives, fields, }
}

///|
fn Parser::parse_object_type_extension(
  self : Parser,
) -> ObjectTypeExtension raise ParseError {
  let position = self.expect_ident("type").position
  let (name, _) = self.expect_name()
  let implements_interfaces = self.parse_implements_interfaces()
  let directives = self.parse_directives()
  let fields = self.parse_fields()
  if implements_interfaces.is_empty() &&
    directives.is_empty() &&
    fields.is_empty() {
    parse_error(
      "Object type extension should contain at least one interface, directive or field.",
      position,
    )
  }
  { position, name, implements_interfaces, directives, fields, }
}

///|
fn Parser::parse_interface_type(
  self : Parser,
  description : String?,
) -> InterfaceType raise ParseError {
  let position = self.expect_ident("interface").position
  let (name, _) = self.expect_name()
  let implements_interfaces = self.parse_implements_interfaces()
  let directives = self.parse_directives()
  let fields = self.parse_fields()
  { position, description, name, implements_interfaces, directives, fields, }
}

///|
fn Parser::parse_interface_type_extension(
  self : Parser,
) -> InterfaceTypeExtension raise ParseError {
  let position = self.expect_ident("interface").position
  let (name, _) = self.expect_name()
  let implements_interfaces = self.parse_implements_interfaces()
  let directives = self.parse_directives()
  let fields = self.parse_fields()
  if directives.is_empty() && fields.is_empty() {
    parse_error(
      "Interface type extension should contain at least one directive or field.",
      position,
    )
  }
  { position, name, implements_interfaces, directives, fields, }
}

///|
fn Parser::parse_union_members(self : Parser) -> Array[String] raise ParseError {
  ignore(self.eat_punct("|"))
  let types : Array[String] = []
  let (name, _) = self.expect_name()
  types.push(name)
  while true {
    if !self.eat_punct("|") {
      break
    }
    let (name, _) = self.expect_name()
    types.push(name)
    continue
  }
  types
}

///|
fn Parser::parse_union_type(
  self : Parser,
  description : String?,
) -> UnionType raise ParseError {
  let position = self.expect_ident("union").position
  let (name, _) = self.expect_name()
  let directives = self.parse_directives()
  let types = if self.eat_punct("=") { self.parse_union_members() } else { [] }
  { position, description, name, directives, types, }
}

///|
fn Parser::parse_union_type_extension(
  self : Parser,
) -> UnionTypeExtension raise ParseError {
  let position = self.expect_ident("union").position
  let (name, _) = self.expect_name()
  let directives = self.parse_directives()
  let has_members = self.eat_punct("=")
  let types = if has_members { self.parse_union_members() } else { [] }
  if directives.is_empty() && !has_members {
    parse_error(
      "Union type extension should contain at least one directive or type.", position,
    )
  }
  { position, name, directives, types, }
}

///|
fn Parser::parse_enum_values(
  self : Parser,
) -> Array[EnumValue] raise ParseError {
  ignore(self.expect_punct("{"))
  let values : Array[EnumValue] = []
  if self.check_punct("}") {
    let token = self.peek()
    parse_error("expected enum value", token.position)
  }
  while true {
    if self.check_punct("}") {
      ignore(self.take())
      break
    }
    let position = self.peek().position
    let description = if self.peek().kind == TkStringValue ||
      self.peek().kind == TkBlockString {
      Some(self.parse_string_literal())
    } else {
      None
    }
    let (name, _) = self.expect_name()
    let directives = self.parse_directives()
    values.push({ position, description, name, directives, })
    continue
  }
  values
}

///|
fn Parser::parse_enum_type(
  self : Parser,
  description : String?,
) -> EnumType raise ParseError {
  let position = self.expect_ident("enum").position
  let (name, _) = self.expect_name()
  let directives = self.parse_directives()
  let values = if self.check_punct("{") { self.parse_enum_values() } else { [] }
  { position, description, name, directives, values, }
}

///|
fn Parser::parse_enum_type_extension(
  self : Parser,
) -> EnumTypeExtension raise ParseError {
  let position = self.expect_ident("enum").position
  let (name, _) = self.expect_name()
  let directives = self.parse_directives()
  let has_values = self.check_punct("{")
  let values = if has_values { self.parse_enum_values() } else { [] }
  if directives.is_empty() && !has_values {
    parse_error(
      "Enum type extension should contain at least one directive or value.", position,
    )
  }
  { position, name, directives, values, }
}

///|
fn Parser::parse_input_fields(
  self : Parser,
) -> Array[InputValue] raise ParseError {
  if !self.eat_punct("{") {
    return []
  }
  let fields : Array[InputValue] = []
  if self.check_punct("}") {
    let token = self.peek()
    parse_error("expected input field", token.position)
  }
  while true {
    if self.check_punct("}") {
      ignore(self.take())
      break
    }
    fields.push(self.parse_input_value())
    continue
  }
  fields
}

///|
fn Parser::parse_input_object_type(
  self : Parser,
  description : String?,
) -> InputObjectType raise ParseError {
  let position = self.expect_ident("input").position
  let (name, _) = self.expect_name()
  let directives = self.parse_directives()
  let fields = self.parse_input_fields()
  { position, description, name, directives, fields, }
}

///|
fn Parser::parse_input_object_type_extension(
  self : Parser,
) -> InputObjectTypeExtension raise ParseError {
  let position = self.expect_ident("input").position
  let (name, _) = self.expect_name()
  let directives = self.parse_directives()
  let fields = self.parse_input_fields()
  if directives.is_empty() && fields.is_empty() {
    parse_error(
      "Input object type extension should contain at least one directive or field.",
      position,
    )
  }
  { position, name, directives, fields, }
}

///|
fn Parser::parse_directive_locations(
  self : Parser,
) -> Array[DirectiveLocation] raise ParseError {
  if !(self.check_punct("|") || self.check_name()) {
    return []
  }
  ignore(self.eat_punct("|"))
  let locations : Array[DirectiveLocation] = []
  let (name, pos) = self.expect_name()
  locations.push(parse_directive_location(name, pos))
  while true {
    if !self.eat_punct("|") {
      break
    }
    let (name, pos) = self.expect_name()
    locations.push(parse_directive_location(name, pos))
    continue
  }
  locations
}

///|
fn Parser::parse_directive_definition(
  self : Parser,
  description : String?,
) -> DirectiveDefinition raise ParseError {
  let position = self.expect_ident("directive").position
  ignore(self.expect_punct("@"))
  let (name, _) = self.expect_name()
  let arguments = self.parse_arguments_definition()
  let repeatable = self.eat_ident("repeatable")
  ignore(self.expect_ident("on"))
  let locations = self.parse_directive_locations()
  { position, description, name, arguments, repeatable, locations, }
}