///|
priv enum State {
  /// We await the start of the stream.
  StreamStart
  ImplicitDocumentStart
  DocumentStart
  DocumentContent
  DocumentEnd
  BlockNode
  // BlockNodeOrIndentlessSequence
  // FlowNode
  BlockSequenceFirstEntry
  BlockSequenceEntry
  IndentlessSequenceEntry
  BlockMappingFirstKey
  BlockMappingKey
  BlockMappingValue
  FlowSequenceFirstEntry
  FlowSequenceEntry
  FlowSequenceEntryMappingKey
  FlowSequenceEntryMappingValue
  FlowSequenceEntryMappingEnd
  FlowMappingFirstKey
  FlowMappingKey
  FlowMappingValue
  FlowMappingEmptyValue
  End
} derive(Eq)

///|
struct Parser {
  lexer : Lexer
  states : Array[State]
  mut state : State
  mut token : Token?
  mut current : (Event, Marker)?
  anchors : @hashmap.HashMap[String, Int]
  mut anchor_id : Int
  /// The tag directives (`%TAG`) the parser has encountered.
  ///
  /// Key is the handle, and value is the prefix.
  mut tags : @hashmap.HashMap[String, String]
  /// Make tags global across all documents.
  mut keep_tags : Bool
}

///|
pub fn Parser::new(str : StringView) -> Parser {
  Parser::{
    lexer: Lexer::new(str),
    states: Array::new(capacity=40),
    state: State::StreamStart,
    token: None,
    current: None,
    anchors: @hashmap.HashMap::new(),
    // valid anchor_id starts from 1
    anchor_id: 1,
    tags: @hashmap.HashMap::new(),
    keep_tags: false,
  }
}

///|
/// Whether to keep tags across multiple documents when parsing.
///
/// This behavior is non-standard as per the YAML specification but can be encountered in the
/// wild. This boolean allows enabling this non-standard extension. This would result in the
/// parser accepting input from [test
/// QLJ7](https://github.com/yaml/yaml-test-suite/blob/ccfa74e56afb53da960847ff6e6976c0a0825709/src/QLJ7.yaml)
/// of the yaml-test-suite:
///
/// ```yaml
/// %TAG !prefix! tag:example.com,2011:
/// --- !prefix!A
/// a: b
/// --- !prefix!B
/// c: d
/// --- !prefix!C
/// e: f
/// ```
///
/// With `keep_tags` set to `false`, the above YAML is rejected. As per the specification, tags
/// only apply to the document immediately following them. This would error on `!prefix!B`.
///
/// With `keep_tags` set to `true`, the above YAML is accepted by the parser.
pub fn Parser::keep_tags(self : Parser, value : Bool) -> Unit {
  self.keep_tags = value
}

///|
/// Try to load the next event and return it, but do not consuming it from `self`.
///
/// Any subsequent call to `Parser::peek` will return the same value, until a call to
/// `Iterator::next` or `Parser::load`.
/// # Errors
/// Returns `YamlError` when loading the next event fails.
fn Parser::peek(self : Parser) -> (Event, Marker) raise YamlError {
  if self.current is Some(x) {
    return x
  } else {
    self.current = Some(self.next_token())
    self.peek()
  }
}

///|
/// Try to load the next event and return it, consuming it from `self`.
/// # Errors
/// Returns `ScanError` when loading the next event fails.
fn Parser::next_token(self : Parser) -> (Event, Marker) raise YamlError {
  let current = self.current
  self.current = None
  match current {
    Some(x) => x
    None => self.parse()
  }
}

///|
test "peek eq parse" {
  let source =
    #|a0 bb: val
    #|a1: &x
    #|    b1: 4
    #|    b2: d
    #|a2: 4
    #|a3: [1, 2, 3]
    #|a4:
    #|    - [a1, a2]
    #|    - 2
    #|a5: *x
  let p = Parser::new(source)
  while true {
    let event_peek = p.peek()
    let event = p.next_token()
    assert_eq(event, event_peek)
    if event.0 is Event::StreamEnd {
      break
    }
  }
}

///|
fn Parser::peek_token(self : Parser) -> Token raise YamlError {
  match self.token {
    None => {
      let token = self.scan_next_token()
      self.token = Some(token)
      token
    }
    Some(tok) => tok
  }
}

///|
fn Parser::scan_next_token(self : Parser) -> Token raise YamlError {
  let token = self.lexer.next()
  match token {
    None =>
      match self.lexer.get_error() {
        None =>
          raise YamlError::YamlError(
            mark=self.lexer.get_marker(),
            info="unexpected eof",
          )
        Some(e) => raise e
      }
    Some(tok) => tok
  }
}

///|
fn Parser::parse(self : Parser) -> (Event, Marker) raise YamlError {
  if self.state == State::End {
    return (Event::StreamEnd, self.lexer.get_marker())
  }
  self.state_machine()
}

///|
/// Load the YAML from the stream in `self`, pushing events into `recv`.
///
/// The contents of the stream are parsed and the corresponding events are sent into the
/// recveiver. For detailed explanations about how events work, see `EventReceiver`.
///
/// If `multi` is set to `true`, the parser will allow parsing of multiple YAML documents
/// inside the stream.
///
/// Note that any `EventReceiver` is also a `MarkedEventReceiver`, so implementing the
/// former is enough to call this function.
/// # Errors
/// Returns `YamlError` when loading fails.
pub fn[R : MarkedEventReceiver] Parser::load(
  self : Parser,
  recv : R,
  multi : Bool,
) -> Unit raise YamlError {
  if !self.lexer.stream_started() {
    let (ev, mark) = self.next_token()
    if ev != Event::StreamStart {
      raise YamlError::YamlError(
        mark~,
        info="did not find expected ",
      )
    }
    recv.on_event(ev, mark)
  }
  if self.lexer.stream_ended() {
    recv.on_event(Event::StreamEnd, self.lexer.get_marker())
    return
  }
  while true {
    let (ev, mark) = self.next_token()
    if ev == Event::StreamEnd {
      recv.on_event(ev, mark)
      return
    }

    // clear anchors before a new document
    self.anchors.clear()
    self.load_document(ev, mark, recv)
    if !multi {
      break
    }
  }
}

///|
fn[R : MarkedEventReceiver] Parser::load_document(
  self : Parser,
  first_ev : Event,
  mark : Marker,
  recv : R,
) -> Unit raise YamlError {
  if first_ev != Event::DocumentStart {
    raise YamlError::YamlError(
      mark~,
      info="did not find expected ",
    )
  }
  recv.on_event(first_ev, mark)
  let (ev, mark) = self.next_token()
  self.load_node(ev, mark, recv)

  // DOCUMENT-END is expected.
  let (ev, mark) = self.next_token()
  guard ev == Event::DocumentEnd
  recv.on_event(ev, mark)
}

///|
fn[R : MarkedEventReceiver] Parser::load_node(
  self : Parser,
  first_ev : Event,
  mark : Marker,
  recv : R,
) -> Unit raise YamlError {
  match first_ev {
    Event::Alias(..) | Event::Scalar(..) => recv.on_event(first_ev, mark)
    Event::SequenceStart(..) => {
      recv.on_event(first_ev, mark)
      self.load_sequence(recv)
    }
    Event::MappingStart(..) => {
      recv.on_event(first_ev, mark)
      self.load_mapping(recv)
    }
    _ => {
      println("UNREACHABLE EVENT: \{first_ev}")
      panic()
    }
  }
}

///|
fn[R : MarkedEventReceiver] Parser::load_sequence(
  self : Parser,
  recv : R,
) -> Unit raise YamlError {
  let (ev, mark) = self.next_token()
  let mut ev = ev
  let mut mark = mark
  while ev != Event::SequenceEnd {
    self.load_node(ev, mark, recv)

    // next event
    let (next_ev, next_mark) = self.next_token()
    ev = next_ev
    mark = next_mark
  }
  recv.on_event(ev, mark)
}

///|
fn[R : MarkedEventReceiver] Parser::load_mapping(
  self : Parser,
  recv : R,
) -> Unit raise YamlError {
  let (key_ev, key_mark) = self.next_token()
  let mut key_ev = key_ev
  let mut key_mark = key_mark
  while key_ev != Event::MappingEnd {
    // key
    self.load_node(key_ev, key_mark, recv)
    // value
    let (ev, mark) = self.next_token()
    self.load_node(ev, mark, recv)

    // next event
    let (ev, mark) = self.next_token()
    key_ev = ev
    key_mark = mark
  }
  recv.on_event(key_ev, key_mark)
}

///|
/// Skip the next token from the scanner.
fn Parser::skip(self : Parser) -> Unit {
  self.token = None
}

///|
fn Parser::state_machine(self : Parser) -> (Event, Marker) raise YamlError {
  match self.state {
    FlowMappingEmptyValue => self.flow_mapping_value(true)
    FlowMappingValue => self.flow_mapping_value(false)
    FlowMappingKey => self.flow_mapping_key(false)
    FlowMappingFirstKey => self.flow_mapping_key(true)
    FlowSequenceEntryMappingEnd => self.flow_sequence_entry_mapping_end()
    FlowSequenceEntryMappingValue => self.flow_sequence_entry_mapping_value()
    FlowSequenceEntryMappingKey => self.flow_sequence_entry_mapping_key()
    FlowSequenceEntry => self.flow_sequence_entry(false)
    FlowSequenceFirstEntry => self.flow_sequence_entry(true)
    BlockMappingValue => self.block_mapping_value()
    BlockMappingKey => self.block_mapping_key(false)
    BlockMappingFirstKey => self.block_mapping_key(true)
    IndentlessSequenceEntry => self.indentless_sequence_entry()
    BlockSequenceEntry => self.block_sequence_entry(false)
    BlockSequenceFirstEntry => self.block_sequence_entry(true)
    BlockNode => self.parse_node(true, false)
    DocumentEnd => self.document_end()
    DocumentContent => self.document_content()
    DocumentStart => self.document_start(false)
    ImplicitDocumentStart => self.document_start(true)
    StreamStart => self.stream_start()
    // impossible case
    End => panic()
  }
}

///|
fn Parser::flow_mapping_key(
  self : Parser,
  first : Bool,
) -> (Event, Marker) raise YamlError {
  if first {
    ignore(self.peek_token())
    self.skip()
  }
  let marker = match self.peek_token() {
    { marker, token_type: TokenType::FlowMappingEnd } => marker
    { marker, token_type: _ } => {
      if !first {
        match self.peek_token() {
          { marker: _, token_type: TokenType::FlowEntry } => self.skip()
          { marker, token_type: _ } =>
            raise YamlError::YamlError(
              mark=marker,
              info="while parsing a flow mapping, did not find expected ',' or '}'",
            )
        }
      }
      match self.peek_token() {
        { marker: _, token_type: TokenType::Key } => {
          self.skip()
          if self.peek_token()
            is {
              marker,
              token_type: TokenType::Value
              | TokenType::FlowEntry
              | TokenType::FlowMappingEnd,
            } {
            self.state = State::FlowMappingValue
            return (Event::empty_scalar(), marker)
          }
          self.push_state(State::FlowMappingValue)
          return self.parse_node(false, false)
        }
        { marker: _, token_type: TokenType::Value } => {
          self.state = State::FlowMappingValue
          return (Event::empty_scalar(), marker)
        }
        { marker: _, token_type: TokenType::FlowMappingEnd } => ()
        _ => {
          self.push_state(State::FlowMappingEmptyValue)
          return self.parse_node(false, false)
        }
      }
      marker
    }
  }
  self.pop_state()
  self.skip()
  (Event::MappingEnd, marker)
}

///|
fn Parser::flow_mapping_value(
  self : Parser,
  empty : Bool,
) -> (Event, Marker) raise YamlError {
  let marker = {
    if empty {
      let { marker, token_type: _ } = self.peek_token()
      self.state = State::FlowMappingKey
      return (Event::empty_scalar(), marker)
    }
    match self.peek_token() {
      { marker, token_type: TokenType::Value } => {
        self.skip()
        match self.peek_token().token_type {
          TokenType::FlowEntry | TokenType::FlowMappingEnd => ()
          _ => {
            self.push_state(State::FlowMappingKey)
            return self.parse_node(false, false)
          }
        }
        marker
      }
      { marker, token_type: _ } => marker
    }
  }
  self.state = State::FlowMappingKey
  (Event::empty_scalar(), marker)
}

///|
fn Parser::block_mapping_key(
  self : Parser,
  first : Bool,
) -> (Event, Marker) raise YamlError {
  // skip BlockMappingStart
  if first {
    ignore(self.peek_token())
    self.skip()
  }
  match self.peek_token() {
    { marker: _, token_type: TokenType::Key } => {
      self.skip()
      if self.peek_token()
        is {
          marker,
          token_type: TokenType::Key
          | TokenType::Value
          | TokenType::BlockEnd,
        } {
        self.state = State::BlockMappingValue
        (Event::empty_scalar(), marker)
      } else {
        self.push_state(State::BlockMappingValue)
        self.parse_node(true, true)
      }
    }
    // libyaml failed to parse spec 1.2, ex8.18
    { marker, token_type: TokenType::Value } => {
      self.state = State::BlockMappingValue
      (Event::empty_scalar(), marker)
    }
    { marker, token_type: TokenType::BlockEnd } => {
      self.pop_state()
      self.skip()
      (Event::MappingEnd, marker)
    }
    { marker, token_type: _ } =>
      raise YamlError::YamlError(
        mark=marker,
        info="while parsing a block mapping, did not find expected key",
      )
  }
}

///|
fn Parser::block_mapping_value(
  self : Parser,
) -> (Event, Marker) raise YamlError {
  match self.peek_token() {
    { marker: _, token_type: TokenType::Value } => {
      self.skip()
      if self.peek_token()
        is {
          marker,
          token_type: TokenType::Key
          | TokenType::Value
          | TokenType::BlockEnd,
        } {
        self.state = State::BlockMappingKey
        (Event::empty_scalar(), marker)
      } else {
        self.push_state(State::BlockMappingKey)
        self.parse_node(true, true)
      }
    }
    { marker, token_type: _ } => {
      self.state = State::BlockMappingKey
      (Event::empty_scalar(), marker)
    }
  }
}

///|
fn Parser::flow_sequence_entry(
  self : Parser,
  first : Bool,
) -> (Event, Marker) raise YamlError {
  // skip FlowMappingStart
  if first {
    ignore(self.peek_token())
    self.skip()
  }
  match self.peek_token() {
    { marker, token_type: TokenType::FlowSequenceEnd } => {
      self.pop_state()
      self.skip()
      return (Event::SequenceEnd, marker)
    }
    { marker: _, token_type: TokenType::FlowEntry } if !first => self.skip()
    { marker, token_type: _ } if !first =>
      raise YamlError::YamlError(
        mark=marker,
        info="while parsing a flow sequence, expected ',' or ']'",
      )
    _ => ()
  }
  match self.peek_token() {
    { marker, token_type: TokenType::FlowSequenceEnd } => {
      self.pop_state()
      self.skip()
      (Event::SequenceEnd, marker)
    }
    { marker, token_type: TokenType::Key } => {
      self.state = State::FlowSequenceEntryMappingKey
      self.skip()
      (Event::MappingStart(id=0, tag=None), marker)
    }
    _ => {
      self.push_state(State::FlowSequenceEntry)
      self.parse_node(false, false)
    }
  }
}

///|
fn Parser::flow_sequence_entry_mapping_key(
  self : Parser,
) -> (Event, Marker) raise YamlError {
  if self.peek_token()
    is {
      marker,
      token_type: TokenType::Value
      | TokenType::FlowEntry
      | TokenType::FlowSequenceEnd,
    } {
    self.skip()
    self.state = State::FlowSequenceEntryMappingValue
    (Event::empty_scalar(), marker)
  } else {
    self.push_state(State::FlowSequenceEntryMappingValue)
    self.parse_node(false, false)
  }
}

///|
fn Parser::flow_sequence_entry_mapping_value(
  self : Parser,
) -> (Event, Marker) raise YamlError {
  match self.peek_token() {
    { marker: _, token_type: TokenType::Value } => {
      self.skip()
      self.state = State::FlowSequenceEntryMappingValue
      if self.peek_token()
        is {
          marker,
          token_type: TokenType::FlowEntry
          | TokenType::FlowSequenceEnd,
        } {
        self.state = State::FlowSequenceEntryMappingEnd
        (Event::empty_scalar(), marker)
      } else {
        self.push_state(State::FlowSequenceEntryMappingEnd)
        self.parse_node(false, false)
      }
    }
    { marker, token_type: _ } => {
      self.state = State::FlowSequenceEntryMappingEnd
      (Event::empty_scalar(), marker)
    }
  }
}

///|
fn Parser::flow_sequence_entry_mapping_end(self : Parser) -> (Event, Marker) {
  self.state = State::FlowSequenceEntry
  (Event::MappingEnd, self.lexer.get_marker())
}

///|
fn Parser::indentless_sequence_entry(
  self : Parser,
) -> (Event, Marker) raise YamlError {
  match self.peek_token() {
    { marker: _, token_type: TokenType::BlockEntry } => ()
    { marker, token_type: _ } => {
      self.pop_state()
      return (Event::SequenceEnd, marker)
    }
  }
  self.skip()
  if self.peek_token()
    is {
      marker,
      token_type: TokenType::BlockEntry
      | TokenType::Key
      | TokenType::Value
      | TokenType::BlockEnd,
    } {
    self.state = State::IndentlessSequenceEntry
    (Event::SequenceEnd, marker)
  } else {
    self.push_state(State::IndentlessSequenceEntry)
    self.parse_node(true, false)
  }
}

///|
fn Parser::block_sequence_entry(
  self : Parser,
  first : Bool,
) -> (Event, Marker) raise YamlError {
  // BLOCK-SEQUENCE-START
  if first {
    let _ = self.peek_token()
    self.skip()
  }
  match self.peek_token() {
    { marker, token_type: TokenType::BlockEnd } => {
      self.pop_state()
      self.skip()
      (Event::SequenceEnd, marker)
    }
    { marker: _, token_type: TokenType::BlockEntry } => {
      self.skip()
      if self.peek_token()
        is { marker, token_type: TokenType::BlockEntry | TokenType::BlockEnd } {
        self.state = State::BlockSequenceEntry
        (Event::empty_scalar(), marker)
      } else {
        self.push_state(State::BlockSequenceEntry)
        self.parse_node(true, false)
      }
    }
    { marker, token_type: _ } =>
      raise YamlError::YamlError(
        mark=marker,
        info="while parsing a block collection, did not find expected '-' indicator",
      )
  }
}

///|
fn Parser::document_end(self : Parser) -> (Event, Marker) raise YamlError {
  let mut explicit_end = false
  let marker = match self.peek_token() {
    { marker, token_type: TokenType::DocumentEnd } => {
      explicit_end = true
      self.skip()
      marker
    }
    { marker, token_type: _ } => marker
  }
  if !self.keep_tags {
    self.tags.clear()
  }
  if explicit_end {
    self.state = State::ImplicitDocumentStart
  } else {
    if self.peek_token()
      is {
        marker,
        token_type: TokenType::VersionDirective(..)
        | TokenType::TagDirective(..),
      } {
      raise YamlError::YamlError(
        mark=marker,
        info="missing explicit document end marker before directive",
      )
    }
    self.state = State::DocumentStart
  }
  (Event::DocumentEnd, marker)
}

///|
fn Parser::document_content(self : Parser) -> (Event, Marker) raise YamlError {
  match self.peek_token() {
    {
      marker,
      token_type: TokenType::VersionDirective(..)
      | TokenType::TagDirective(..)
      | TokenType::DocumentStart
      | TokenType::DocumentEnd
      | TokenType::StreamEnd,
    } => {
      self.pop_state()
      // empty scalar
      (Event::empty_scalar(), marker)
    }
    _ => self.parse_node(true, false)
  }
}

///|
fn Parser::parse_node(
  self : Parser,
  block : Bool,
  indentless_sequence : Bool,
) -> (Event, Marker) raise YamlError {
  let mut anchor_id = 0
  let mut tag = None
  match self.peek_token() {
    { marker: _, token_type: TokenType::Alias(_) } => {
      self.pop_state()
      if self.fetch_token() is { marker, token_type: TokenType::Alias(name) } {
        match self.anchors.get(name) {
          None =>
            raise YamlError::YamlError(
              mark=marker,
              info="while parsing node, found unknown anchor",
            )
          Some(id) => return (Event::Alias(id~), marker)
        }
      }
      panic()
    }
    { marker: _, token_type: TokenType::Anchor(_) } =>
      if self.fetch_token() is { marker, token_type: TokenType::Anchor(name) } {
        anchor_id = self.register_anchor(name, marker)
        if self.peek_token().token_type is TokenType::Tag(..) {
          if self.fetch_token().token_type is TokenType::Tag(handle~, suffix~) {
            tag = Some(self.resolve_tag(marker, handle, suffix))
          } else {
            panic()
          }
        }
      } else {
        panic()
      }
    { marker, token_type: TokenType::Tag(..) } =>
      if self.fetch_token().token_type is TokenType::Tag(handle~, suffix~) {
        tag = Some(self.resolve_tag(marker, handle, suffix))
        if self.peek_token().token_type is TokenType::Anchor(_) {
          if self.fetch_token()
            is { marker, token_type: TokenType::Anchor(name) } {
            anchor_id = self.register_anchor(name, marker)
          } else {
            panic()
          }
        }
      } else {
        panic()
      }
    _ => ()
  }
  match self.peek_token() {
    { marker, token_type: TokenType::BlockEntry } if indentless_sequence => {
      self.state = State::IndentlessSequenceEntry
      (Event::SequenceStart(id=anchor_id, tag~), marker)
    }
    { marker: _, token_type: TokenType::Scalar(_) } => {
      self.pop_state()
      if self.fetch_token()
        is { marker, token_type: TokenType::Scalar(style, value) } {
        (Event::Scalar(value~, style~, id=anchor_id, tag~), marker)
      } else {
        panic()
      }
    }
    { marker, token_type: TokenType::FlowSequenceStart } => {
      self.state = State::FlowSequenceFirstEntry
      (Event::SequenceStart(id=anchor_id, tag~), marker)
    }
    { marker, token_type: TokenType::FlowMappingStart } => {
      self.state = State::FlowMappingFirstKey
      (Event::MappingStart(id=anchor_id, tag~), marker)
    }
    { marker, token_type: TokenType::BlockSequenceStart } if block => {
      self.state = State::BlockSequenceFirstEntry
      (Event::SequenceStart(id=anchor_id, tag~), marker)
    }
    { marker, token_type: TokenType::BlockMappingStart } if block => {
      self.state = State::BlockMappingFirstKey
      (Event::MappingStart(id=anchor_id, tag~), marker)
    }
    // ex 7.2, an empty scalar can follow a secondary tag
    { marker, token_type: _ } if tag is Some(_) || anchor_id > 0 => {
      self.pop_state()
      (Event::empty_scalar_with_anchor(anchor_id, tag), marker)
    }
    { marker, token_type: _ } =>
      raise YamlError::YamlError(
        mark=marker,
        info="while parsing a node, did not find expected node content",
      )
  }
}

///|
/// Resolve a tag from the handle and the suffix.
fn Parser::resolve_tag(
  self : Parser,
  mark : Marker,
  handle : String,
  suffix : String,
) -> Tag raise YamlError {
  if handle == "!!" {
    // "!!" is a shorthand for "tag:yaml.org,2002:". However, that default can be
    // overridden.
    match self.tags.get("!!") {
      Some(prefix) => Tag::{ handle: prefix, suffix }
      None => Tag::{ handle: "tag:yaml.org,2002:", suffix }
    }
  } else if handle.is_empty() && suffix == "!" {
    // "!" introduces a local tag. Local tags may have their prefix overridden.
    match self.tags.get("") {
      Some(prefix) => Tag::{ handle: prefix, suffix }
      None => Tag::{ handle: "", suffix }
    }
  } else {
    // Lookup handle in our tag directives.
    let prefix = self.tags.get(handle)
    if prefix is Some(prefix) {
      Tag::{ handle: prefix, suffix }
      // Otherwise, it may be a local handle. With a local handle, the handle is set to
      // "!" and the suffix to whatever follows it ("!foo" -> ("!", "foo")).
      // If the handle is of the form "!foo!", this cannot be a local handle and we need
      // to error.
    } else if handle.length() >= 2 &&
      handle.has_prefix("!") &&
      handle.has_suffix("!") {
      raise YamlError::YamlError(mark~, info="the handle wasn't declared")
    } else {
      Tag::{ handle, suffix }
    }
  }
}

///|
fn Parser::register_anchor(self : Parser, name : String, _ : Marker) -> Int {
  // anchors can be overridden/reused
  let new_id = self.anchor_id
  self.anchor_id += 1
  self.anchors[name] = new_id
  new_id
}

///|
fn Parser::fetch_token(self : Parser) -> Token {
  let token = self.token
  self.token = None
  token.unwrap_or_else(() => {
    println("fetch_token needs to be preceded by peek_token")
    panic()
  })
}

///|
fn Parser::pop_state(self : Parser) -> Unit {
  self.state = self.states.pop().unwrap()
}

///|
fn Parser::document_start(
  self : Parser,
  implicit : Bool,
) -> (Event, Marker) raise YamlError {
  while self.peek_token().token_type is TokenType::DocumentEnd {
    self.skip()
  }
  match self.peek_token() {
    { marker, token_type: TokenType::StreamEnd } => {
      self.state = State::End
      self.skip()
      (Event::StreamEnd, marker)
    }
    {
      marker: _,
      token_type: TokenType::VersionDirective(_)
      | TokenType::TagDirective(_)
      | TokenType::DocumentStart,
    } =>
      // explicit document
      self.explicit_document_start()
    { marker, .. } if implicit => {
      self.parser_process_directives()
      self.push_state(State::DocumentEnd)
      self.state = State::BlockNode
      (Event::DocumentStart, marker)
    }
    _ => self.explicit_document_start()
  }
}

///|
fn Parser::push_state(self : Parser, state : State) -> Unit {
  self.states.push(state)
}

///|
fn Parser::explicit_document_start(
  self : Parser,
) -> (Event, Marker) raise YamlError {
  self.parser_process_directives()
  match self.peek_token() {
    { marker, token_type: TokenType::DocumentStart } => {
      self.push_state(State::DocumentEnd)
      self.state = State::DocumentContent
      self.skip()
      (Event::DocumentStart, marker)
    }
    { marker, .. } =>
      raise YamlError::YamlError(
        mark=marker,
        info="did not find expected ",
      )
  }
}

///|
fn Parser::parser_process_directives(self : Parser) -> Unit raise YamlError {
  let mut version_directive_received = false
  while true {
    let tags = @hashmap.HashMap::new()
    match self.peek_token() {
      { marker, token_type: TokenType::VersionDirective(..) } => {
        // XXX parsing with warning according to spec
        if version_directive_received {
          raise YamlError::YamlError(
            mark=marker,
            info="duplicate version directive",
          )
        }
        version_directive_received = true
      }
      { marker, token_type: TokenType::TagDirective(handle~, prefix~) } => {
        if tags.contains(handle) {
          raise YamlError::YamlError(
            mark=marker,
            info="the TAG directive must only be given at most once per handle in the same document",
          )
        }
        tags[handle] = prefix
      }
      _ => break
    }
    self.tags = tags
    self.skip()
  }
}

///|
fn Parser::stream_start(self : Parser) -> (Event, Marker) raise YamlError {
  match self.peek_token() {
    { marker, token_type: TokenType::StreamStart } => {
      self.state = State::ImplicitDocumentStart
      self.skip()
      (Event::StreamStart, marker)
    }
    { marker, .. } =>
      raise YamlError::YamlError(
        mark=marker,
        info="did not find expected ",
      )
  }
}