///|
/// Small character cursor shared by the line and label parsers.
priv struct TextCursor {
  chars : Array[Char]
  mut index : Int
}

///|
fn TextCursor::new(source : StringView) -> TextCursor {
  { chars: source.to_array(), index: 0 }
}

///|
fn TextCursor::is_end(self : TextCursor) -> Bool {
  self.index >= self.chars.length()
}

///|
fn TextCursor::position(self : TextCursor) -> Int {
  self.index
}

///|
fn TextCursor::peek(self : TextCursor) -> Char? {
  if self.is_end() {
    None
  } else {
    Some(self.chars[self.index])
  }
}

///|
fn TextCursor::next(self : TextCursor) -> Char? {
  if self.is_end() {
    None
  } else {
    let value = self.chars[self.index]
    self.index += 1
    Some(value)
  }
}

///|
fn TextCursor::skip_horizontal_space(self : TextCursor) -> Unit {
  while !self.is_end() {
    match self.peek() {
      Some(' ' | '\t') => self.index += 1
      _ => return
    }
  }
}

///|
fn TextCursor::take_horizontal_space(self : TextCursor) -> Bool {
  let before = self.index
  self.skip_horizontal_space()
  self.index > before
}

///|
fn TextCursor::consume(self : TextCursor, expected : Char) -> Bool {
  match self.peek() {
    Some(actual) =>
      if actual == expected {
        self.index += 1
        true
      } else {
        false
      }
    None => false
  }
}

///|
fn TextCursor::read_label_name(self : TextCursor) -> String {
  let out = StringBuilder()
  while !self.is_end() {
    let char = self.chars[self.index]
    if char.is_ascii_alphabetic() || char.is_ascii_digit() || char == '_' {
      out.write_char(char)
      self.index += 1
    } else {
      break
    }
  }
  out.to_string()
}

///|
fn TextCursor::read_metric_name(self : TextCursor) -> String {
  let out = StringBuilder()
  while !self.is_end() {
    let char = self.chars[self.index]
    if char.is_ascii_alphabetic() ||
      char.is_ascii_digit() ||
      char == '_' ||
      char == ':' {
      out.write_char(char)
      self.index += 1
    } else {
      break
    }
  }
  out.to_string()
}

///|
fn TextCursor::read_token(self : TextCursor) -> String {
  let out = StringBuilder()
  while !self.is_end() {
    let char = self.chars[self.index]
    if char == ' ' || char == '\t' || char == '#' {
      break
    }
    out.write_char(char)
    self.index += 1
  }
  out.to_string()
}

///|
fn TextCursor::remaining(self : TextCursor) -> String {
  let out = StringBuilder()
  for index in self.index..