///|
/// A parsed JSON value. `Number` retains its input token until the JCS number
/// serializer is applied; this prevents an early, host-specific conversion.
pub enum JsonValue {
  Null
  Bool(Bool)
  Number(String)
  String(String)
  Array(Array[JsonValue])
  Object(Array[(String, JsonValue)])
}

///|
/// A source-positioned reason why strict JSON parsing failed.
pub enum ParseError {
  UnexpectedEnd(Int)
  UnexpectedCharacter(Int, Char)
  Expected(Int, String)
  InvalidEscape(Int, String)
  InvalidNumber(Int, String)
  DuplicateKey(Int, String)
  TrailingContent(Int)
}

///|
/// A value that cannot be represented by JCS canonical JSON.
pub enum CanonicalError {
  InvalidNumber(String, String)
  NonFiniteNumber(String)
}

///|
pub fn CanonicalError::message(self : CanonicalError) -> String {
  match self {
    InvalidNumber(value, reason) =>
      "cannot canonicalize number '" + value + "': " + reason
    NonFiniteNumber(value) =>
      "number '" + value + "' is outside IEEE-754 finite range"
  }
}

///|
pub fn ParseError::message(self : ParseError) -> String {
  match self {
    UnexpectedEnd(position) =>
      "unexpected end of JSON at character " + position.to_string()
    UnexpectedCharacter(position, char) =>
      "unexpected character '" +
      char.to_string() +
      "' at character " +
      position.to_string()
    Expected(position, expected) =>
      "expected " + expected + " at character " + position.to_string()
    InvalidEscape(position, reason) =>
      "invalid string escape at character " +
      position.to_string() +
      ": " +
      reason
    InvalidNumber(position, reason) =>
      "invalid number at character " + position.to_string() + ": " + reason
    DuplicateKey(position, key) =>
      "duplicate object key '" + key + "' at character " + position.to_string()
    TrailingContent(position) =>
      "trailing content at character " + position.to_string()
  }
}

///|
priv struct Parser {
  chars : Array[Char]
  mut position : Int
}

///|
fn Parser::new(input : String) -> Parser {
  let chars : Array[Char] = []
  for char in input {
    chars.push(char)
  }
  { chars, position: 0 }
}