///|
// Jsonnet standard-library string manifests have sorted keys and spaces even
// in compact form. Ordinary CLI JSON has no whitespace contract.
fn render_json(
  j : Json,
  pretty : Bool,
  depth : Int,
  limit : Int,
  loc : Location,
) -> String raise {
  let b = StringBuilder()
  let mut bytes = 0
  let write = fn(s : String) raise {
    let size = @utf8.encode(s).length()
    if size > limit - bytes {
      raise problem(Limit, loc, "rendered JSON string exceeds output limit")
    }
    bytes += size
    b.write_string(s)
  }
  let indent = fn(n) raise {
    for _ in 0.. {
      write("[")
      if items.is_empty() {
        write(if pretty { "\n\n" } else { " " })
      }
      for i, item in items {
        separator(i == 0)
        write(render_json(item, pretty, depth + 1, limit - bytes, loc))
      }
      if pretty && !items.is_empty() {
        write("\n")
        indent(depth * 4)
      }
      write("]")
    }
    Object(fields) => {
      let keys = fields.keys().collect()
      keys.sort_by(compare_unicode)
      write("{")
      if keys.is_empty() {
        write(if pretty { "\n\n" } else { " " })
      }
      for i, key in keys {
        separator(i == 0)
        write(manifest_text_quote(key))
        write(": ")
        write(render_json(fields[key], pretty, depth + 1, limit - bytes, loc))
      }
      if pretty && !keys.is_empty() {
        write("\n")
        indent(depth * 4)
      }
      write("}")
    }
    Number(n, ..) => {
      let text = if pretty || n.is_inf() || n.is_nan() {
        manifest_number(n)
      } else {
        @number.format(n)
      }
      if text.is_empty() {
        fail("cannot format number in C numeric locale")
      }
      write(text)
    }
    String(text) => write(manifest_text_quote(text))
    _ => write(j.stringify())
  }
  b.to_string()
}

///|
// Go's manifestJson uses shortest round-trip digits in fixed decimal notation.
// Go's sum/avg may also produce non-finite values. Core supplies finite digits;
// expand its exponent and restore negative zero, which core prints as "0".
fn manifest_number(n : Double) -> String {
  if n.is_nan() {
    return "NaN"
  }
  if n.is_inf() {
    return if n < 0 { "-Inf" } else { "+Inf" }
  }
  let sign = if n.reinterpret_as_int64() < 0 { "-" } else { "" }
  let text = n.abs().to_string()
  guard text.find("e") is Some(e) else { return sign + text }
  let digits = text[:e].to_owned().replace(old=".", new="")
  let mut exponent = 0
  for c in text[e + 2:].iter() {
    exponent = exponent * 10 + c.to_int() - 48
  }
  if text.code_unit_at(e + 1) == 45 {
    sign + "0." + "0".repeat(exponent - 1) + digits
  } else {
    sign + digits + "0".repeat(exponent + 1 - digits.length())
  }
}