///|
priv struct Parser {
  input : StringView
  scripting_enabled : Bool
  xml_coercion : Bool
  track_node_locations : Bool
  mut pos : Int
  errors : Array[@core.ParseError]
  root : @dom.Node
  stack : Array[@dom.Node]
  mut form_element : @dom.Node?
  mut frameset_ok : Bool
  mut frameset_reprocesses_in_body_mode : Bool
  mut detached_frameset_mode : Bool
  mut before_html_phase : Bool
  mut after_frameset : Bool
  mut document_after_body : Bool
  mut select_insertion_mode : Bool
  mut foreign_table_row_recovery_mode : Bool
  foster_parented_start_error_nodes : Array[@dom.Node]
  mut fragment_context_table : @dom.Node?
  mut fragment_context_table_section : @dom.Node?
  mut fragment_context_table_row : @dom.Node?
  mut fragment_context_table_cell : @dom.Node?
  mut fragment_context_caption : Bool
  mut fragment_context_colgroup : Bool
  mut fragment_context_html : Bool
  mut fragment_context_html_root : @dom.Node?
  mut fragment_context_html_after_body : Bool
  mut fragment_context_foreign : @dom.Node?
  template_column_group_templates : Array[@dom.Node]
  template_table_context_templates : Array[@dom.Node]
  mut template_table_context_detached_mode : Bool
  mut template_detached_column_group_mode : Bool
  mut deferred_initial_doctype_error : @core.ParseError?
  mut duplicate_head_element : @dom.Node?
  mut repeated_document_html_shell : Bool
  pending_document_html_attrs : Map[String, String?]
  mut pending_table_view_implied_end_tag : Bool
  post_body_html_children : Array[@dom.Node]
  active_formatting : Array[FormattingEntry]
}

///|
priv struct FormattingEntry {
  marker : Bool
  name : String
  ns : String
  attrs : Map[String, String?]
  node : @dom.Node
}

///|
fn Parser::new(
  input : StringView,
  root : @dom.Node,
  scripting_enabled~ : Bool,
  xml_coercion~ : Bool,
  track_node_locations? : Bool = false,
) -> Parser {
  let input = strip_leading_bom(input)
  {
    input,
    scripting_enabled,
    xml_coercion,
    track_node_locations,
    pos: 0,
    errors: [],
    root,
    stack: [root],
    form_element: None,
    frameset_ok: root.kind == Document,
    frameset_reprocesses_in_body_mode: false,
    detached_frameset_mode: false,
    before_html_phase: root.kind == Document,
    after_frameset: false,
    document_after_body: false,
    select_insertion_mode: false,
    foreign_table_row_recovery_mode: false,
    foster_parented_start_error_nodes: [],
    fragment_context_table: None,
    fragment_context_table_section: None,
    fragment_context_table_row: None,
    fragment_context_table_cell: None,
    fragment_context_caption: false,
    fragment_context_colgroup: false,
    fragment_context_html: false,
    fragment_context_html_root: None,
    fragment_context_html_after_body: false,
    fragment_context_foreign: None,
    template_column_group_templates: [],
    template_table_context_templates: [],
    template_table_context_detached_mode: false,
    template_detached_column_group_mode: false,
    deferred_initial_doctype_error: None,
    duplicate_head_element: None,
    repeated_document_html_shell: false,
    pending_document_html_attrs: {},
    pending_table_view_implied_end_tag: false,
    post_body_html_children: [],
    active_formatting: [],
  }
}

///|
fn Parser::current(self : Parser) -> Char? {
  self.input.get_char(self.pos)
}

///|
fn Parser::is_eof(self : Parser) -> Bool {
  self.pos >= self.input.length()
}

///|
fn Parser::advance_char(self : Parser) -> Char? {
  match self.current() {
    Some(ch) => {
      self.pos += ch.utf16_len()
      Some(ch)
    }
    None => None
  }
}

///|
fn Parser::starts_with(self : Parser, prefix : StringView) -> Bool {
  let end = self.pos + prefix.length()
  match self.input.get_view(start=self.pos, end~) {
    Some(view) => view == prefix
    None => false
  }
}

///|
fn Parser::skip_ascii_whitespace(self : Parser) -> Unit {
  while !self.is_eof() {
    match self.current() {
      Some(ch) if ch.is_ascii_whitespace() => ignore(self.advance_char())
      _ => return
    }
  }
}

///|
fn Parser::current_node(self : Parser) -> @dom.Node {
  match self.stack.last() {
    Some(node) => node
    None => self.root
  }
}