///|
/// Line parser state and basic helpers.

///|
priv struct LineParser {
  lines : Array[String]
  mut index : Int32
  offsets : Array[Int32]
  mut func_depth : Int32
  mut async_depth : Int32
  mut block_depth : Int32
  mut last_dedent_from : Int32?
}

///|
fn LineParser::new(source : String) -> LineParser {
  let lines : Array[String] = []
  for line in source.split("\n") {
    lines.push(line.to_owned())
  }
  let offsets : Array[Int32] = []
  let mut offset = 0
  for line in lines {
    offsets.push(offset)
    offset += line.length() + 1
  }
  LineParser::{
    lines,
    index: 0,
    offsets,
    func_depth: 0,
    async_depth: 0,
    block_depth: 0,
    last_dedent_from: None,
  }
}

///|
fn LineParser::invalid_syntax_at_line(
  self : LineParser,
  line : Int32,
) -> ParseError {
  let line_index = if line <= 0 { 0 } else { line - 1 }
  let base = self.offsets[line_index]
  make_parse_error(ParseErrorKind::Syntax, "invalid syntax".to_string(), {
    kind: Eof,
    start: base,
    end: base + 1,
    line,
    column: 1,
  })
}

///|
fn LineParser::peek_next_indent(self : LineParser) -> Int32 {
  let mut idx = self.index
  while idx < self.lines.length() {
    let line = self.lines[idx]
    let trimmed = line.trim(chars=" \t").to_owned()
    if trimmed.length() == 0 || trimmed.has_prefix("#") {
      idx += 1
      continue
    }
    return count_indent(line)
  }
  0
}

///|
fn LineParser::peek_next_significant_index(self : LineParser) -> Int32? {
  let mut idx = self.index
  while idx < self.lines.length() {
    let line = self.lines[idx]
    let trimmed = line.trim(chars=" \t").to_owned()
    if trimmed.length() == 0 || trimmed.has_prefix("#") {
      idx += 1
      continue
    }
    return Some(idx)
  }
  None
}

///|
fn LineParser::clamp_line_index(self : LineParser, idx : Int32) -> Int32 {
  if idx < 0 {
    return 0
  }
  if idx >= self.lines.length() {
    if self.lines.length() == 0 {
      return 0
    }
    return self.lines.length() - 1
  }
  idx
}