///|
/// TOML value types
pub enum TomlValue {
  String(String)
  Integer(Int64)
  Float(Double)
  Boolean(Bool)
  DateTime(String) // ISO 8601 datetime string
  Array(Array[TomlValue])
  Table(Map[String, TomlValue])
} derive(Eq, Show)

///|
/// Parser error type
pub(all) suberror ParseError {
  UnexpectedChar(Int, Char) // position, char
  UnexpectedEof
  InvalidEscape(Int, Char)
  InvalidNumber(Int, String)
  InvalidDateTime(Int, String)
  DuplicateKey(String)
  InvalidKey(Int, String)
} derive(Eq, Show)

///|
/// Parser state
priv struct Parser {
  input : String
  mut pos : Int
}

///|
/// Create a new parser
fn Parser::new(input : String) -> Parser {
  { input, pos: 0 }
}

///|
/// Check if at end of input
fn Parser::is_eof(self : Parser) -> Bool {
  self.pos >= self.input.length()
}

///|
/// Peek current character without consuming
fn Parser::peek(self : Parser) -> Char? {
  if self.is_eof() {
    None
  } else {
    self.input.get_char(self.pos)
  }
}

///|
/// Get current character and advance
fn Parser::next(self : Parser) -> Char? {
  match self.peek() {
    Some(ch) => {
      self.pos += 1
      Some(ch)
    }
    None => None
  }
}

///|
/// Skip whitespace and comments
fn Parser::skip_whitespace(self : Parser) -> Unit {
  while not(self.is_eof()) {
    match self.peek() {
      Some(' ' | '\t' | '\r') => {
        self.pos += 1
        continue
      }
      Some('#') =>
        // Skip comment until end of line
        while not(self.is_eof()) {
          match self.next() {
            Some('\n') => break
            _ => continue
          }
        }
      _ => break
    }
  }
}