///|
/// A Racket datum: the value an atom of shrubbery notation denotes.
///
/// `Pair` and `Nil` exist because a `#{...}` escape can contain an improper
/// list. `Rat` exists because `1/2` is an exact rational and turning it into a
/// `Double` would lose the exactness the reference keeps.
pub(all) enum Datum {
  Sym(String)
  Kw(String)
  Str(String)
  Bs(Bytes)
  Ch(Char)
  Bool_(Bool)
  /// Racket's `(void)`, which shrubbery spells `#void`.
  Void
  Int_(@bigint.BigInt)
  /// Numerator and denominator, already in lowest terms with a positive
  /// denominator.
  Rat(@bigint.BigInt, @bigint.BigInt)
  Flo(Double)
  Nil
  Pair(Datum, Datum)
  Vec(Array[Datum])
  /// A regular expression: `#rx"..."` or `#px"..."`. The flag says which.
  Rx(Bool, String)
  /// A datum this reader knows how to keep but not how to interpret: a box, a
  /// hash table, a complex number. Held as the text Racket would print for it,
  /// so it round-trips, and opaque because nothing downstream inspects it.
  Other(String)
} derive(Eq)

///|
/// A canonical text form, for comparing our parse against the reference's.
///
/// Deliberately NOT Racket's `write`. Reproducing Racket's flonum printing
/// byte for byte is a real piece of work — shortest-round-trip digits, the
/// forced `.0`, the exponent thresholds — and getting it wrong would show up
/// as a parse-parity failure that is really a formatting bug, which is the
/// worst kind of red herring to hand someone. So a flonum is written as its
/// IEEE-754 bits and the question does not arise. The oracle emits the same
/// form from the Racket side.
///
/// Where the reference's exact printing DOES matter is `write_shrubbery`, and
/// there it is the thing under test rather than the measuring instrument.
pub fn Datum::canonical(self : Datum) -> String {
  let buf = StringBuilder()
  self.write_canonical(buf)
  buf.to_string()
}

///|
pub fn Datum::write_canonical(self : Datum, buf : StringBuilder) -> Unit {
  match self {
    Sym(s) => {
      buf.write_char('|')
      write_escaped(buf, s)
      buf.write_char('|')
    }
    Kw(s) => {
      buf.write_string("#:|")
      write_escaped(buf, s)
      buf.write_char('|')
    }
    Str(s) => {
      buf.write_char('"')
      write_escaped(buf, s)
      buf.write_char('"')
    }
    Bs(b) => {
      buf.write_string("#\"")
      for i in 0.. {
      buf.write_string("#\\u{")
      buf.write_string(c.to_int().to_string(radix=16))
      buf.write_char('}')
    }
    Bool_(v) => buf.write_string(if v { "#t" } else { "#f" })
    Void => buf.write_string("#")
    Int_(n) => buf.write_string(n.to_string())
    Rat(n, d) => {
      buf.write_string(n.to_string())
      buf.write_char('/')
      buf.write_string(d.to_string())
    }
    Flo(d) =>
      // Every NaN is the same value as far as the notation is concerned, and
      // MoonBit's and Racket's differ in the payload bits. Writing the bits of
      // one would report a difference that is not one.
      if d != d {
        buf.write_string("#f64:nan")
      } else {
        buf.write_string("#f64:")
        let bits = d.reinterpret_as_uint64()
        let hex = bits.to_string(radix=16)
        for _ in 0..<(16 - hex.length()) {
          buf.write_char('0')
        }
        buf.write_string(hex)
      }
    Nil => buf.write_string("()")
    Pair(a, b) => {
      buf.write_char('(')
      a.write_canonical(buf)
      buf.write_string(" . ")
      b.write_canonical(buf)
      buf.write_char(')')
    }
    Rx(_, pattern) => {
      // Racket's `object-name` of a regexp is its pattern string, which is what
      // the oracle prints; the `#rx` / `#px` distinction is not part of it.
      buf.write_string("#')
    }
    Other(text) => {
      buf.write_string("#')
    }
    Vec(xs) => {
      buf.write_string("#(")
      for i in 0.. 0 {
          buf.write_char(' ')
        }
        xs[i].write_canonical(buf)
      }
      buf.write_char(')')
    }
  }
}

///|
/// Escape so that the result is unambiguous ASCII: `\`, `"` and `|` get a
/// backslash, and everything outside printable ASCII becomes `\u{...}`.
///
/// All-ASCII on purpose. The comparison is against text produced by another
/// process, and an encoding difference anywhere between the two would otherwise
/// show up as a parse disagreement.
fn write_escaped(buf : StringBuilder, s : String) -> Unit {
  for c in s {
    let u = c.to_int()
    if c == '\\' || c == '"' || c == '|' {
      buf.write_char('\\')
      buf.write_char(c)
    } else if u >= 0x20 && u < 0x7F {
      buf.write_char(c)
    } else {
      buf.write_string("\\u{")
      buf.write_string(u.to_string(radix=16))
      buf.write_char('}')
    }
  }
}

///|
fn write_hex2(buf : StringBuilder, v : Int) -> Unit {
  let hex = v.to_string(radix=16)
  if hex.length() < 2 {
    buf.write_char('0')
  }
  buf.write_string(hex)
}

///|
/// An exact integer.
pub fn Datum::of_int(n : Int) -> Datum {
  Int_(@bigint.BigInt::from_int(n))
}

///|
/// An exact rational, reduced, with the sign on the numerator.
///
/// Reduces here rather than trusting the caller because the reference's reader
/// produces reduced rationals and the canonical form has to match: `2/4` and
/// `1/2` are the same number and must not compare as different parses.
pub fn Datum::of_ratio(num : @bigint.BigInt, den : @bigint.BigInt) -> Datum {
  let zero = @bigint.BigInt::from_int(0)
  let mut n = num
  let mut d = den
  if d < zero {
    n = -n
    d = -d
  }
  let g = gcd(if n < zero { -n } else { n }, d)
  if g > @bigint.BigInt::from_int(1) {
    n = n / g
    d = d / g
  }
  if d == @bigint.BigInt::from_int(1) {
    Int_(n)
  } else {
    Rat(n, d)
  }
}

///|
fn gcd(a : @bigint.BigInt, b : @bigint.BigInt) -> @bigint.BigInt {
  let zero = @bigint.BigInt::from_int(0)
  let mut x = a
  let mut y = b
  while y != zero {
    let t = x % y
    x = y
    y = t
  }
  x
}