///|
/// A JSON string. `text` is the decoded value. `raw` is the source spelling
/// with its quotes, or `None` for a constructed value. Logical comparison
/// and canonical writing use only `text`.
pub struct Str {
  text : String
  raw : String?
} derive(Eq, Debug)

///|
pub extend Str with Eq::{equal, not_equal}

///|
pub extend Str with @debug.Debug::{to_repr}

///|
pub fn Str::new(text : String) -> Str {
  { text, raw: None, }
}

///|
/// The kind and content of one JSON value.
pub(all) enum ValueKind {
  /// Member occurrences in source order. Repeated names stay separate.
  Object(ReadOnlyArray[Member])
  Array(ReadOnlyArray[Value])
  String(Str)
  /// The exact number lexeme. No floating-point value is computed.
  Number(String)
  Boolean(Bool)
  Null
} derive(Eq, Debug)

///|
pub extend ValueKind with Eq::{equal, not_equal}

///|
pub extend ValueKind with @debug.Debug::{to_repr}

///|
/// One JSON value with its source span.
pub struct Value {
  kind : ValueKind
  span : @diagrams.Span?
} derive(Eq, Debug)

///|
pub extend Value with Eq::{equal, not_equal}

///|
pub extend Value with @debug.Debug::{to_repr}

///|
/// One object member occurrence.
pub struct Member {
  name : Str
  value : Value
  span : @diagrams.Span?
} derive(Eq, Debug)

///|
pub extend Member with Eq::{equal, not_equal}

///|
pub extend Member with @debug.Debug::{to_repr}

///|
pub fn Member::new(name : String, value : Value) -> Member {
  { name: Str::new(name), value, span: None, }
}

///|
/// An object. The constructor copies `members`.
pub fn Value::object(members : ArrayView[Member]) -> Value {
  { kind: Object(ReadOnlyArray::from_array(members)), span: None, }
}

///|
/// An array. The constructor copies `items`.
pub fn Value::array(items : ArrayView[Value]) -> Value {
  { kind: Array(ReadOnlyArray::from_array(items)), span: None, }
}

///|
pub fn Value::string(text : String) -> Value {
  { kind: String(Str::new(text)), span: None, }
}

///|
/// A number with an exact decimal lexeme, for example `-0.0` or `1e400`.
/// Writing gives an error when the lexeme is not a valid JSON number.
pub fn Value::number(lexeme : String) -> Value {
  { kind: Number(lexeme), span: None, }
}

///|
pub fn Value::boolean(value : Bool) -> Value {
  { kind: Boolean(value), span: None, }
}

///|
pub fn Value::null() -> Value {
  { kind: Null, span: None, }
}

///|
/// One parsed JSON diagram block.
pub struct Document {
  name : String?
  statements : ReadOnlyArray[@envelope.Statement]
  highlights : ReadOnlyArray[@data.Selector]
  value : Value
} derive(Eq, Debug)

///|
pub extend Document with Eq::{equal, not_equal}

///|
pub extend Document with @debug.Debug::{to_repr}

///|
/// A document. The constructor copies its arrays.
pub fn Document::new(
  value : Value,
  name? : String,
  statements~ : ArrayView[@envelope.Statement] = [],
  highlights~ : ArrayView[@data.Selector] = [],
) -> Document {
  {
    name,
    statements: ReadOnlyArray::from_array(statements),
    highlights: ReadOnlyArray::from_array(highlights),
    value,
  }
}

///|
/// The kind name of a value: `object`, `array`, `string`, `number`,
/// `boolean` or `null`.
pub fn Value::kind_name(self : Value) -> String {
  match self.kind {
    Object(_) => "object"
    Array(_) => "array"
    String(_) => "string"
    Number(_) => "number"
    Boolean(_) => "boolean"
    Null => "null"
  }
}

///|
/// Compare two values without spans and source spellings. The comparison
/// uses an explicit stack, so deep values do not use the call stack.
pub fn Value::logical_equal(self : Value, other : Value) -> Bool {
  let stack = [(self, other)]
  while stack.pop() is Some((a, b)) {
    match (a.kind, b.kind) {
      (Object(x), Object(y)) => {
        if x.length() != y.length() {
          return false
        }
        for i in 0.. {
        if x.length() != y.length() {
          return false
        }
        for i in 0.. if x.text != y.text { return false }
      (Number(x), Number(y)) => if x != y { return false }
      (Boolean(x), Boolean(y)) => if x != y { return false }
      (Null, Null) => ()
      _ => return false
    }
  }
  true
}

///|
/// Compare two documents without spans and source spellings.
pub fn Document::logical_equal(self : Document, other : Document) -> Bool {
  if self.name != other.name ||
    self.statements != other.statements ||
    self.highlights.length() != other.highlights.length() {
    return false
  }
  for i in 0..