// Canonical AST dumps used to compare against the Python implementation.

///|
/// Python `json.dumps(s, ensure_ascii=False)`.
pub fn json_quote(s : String) -> String {
  let sb = StringBuilder()
  sb.write_char('"')
  for c in s {
    match c {
      '"' => sb.write_string("\\\"")
      '\\' => sb.write_string("\\\\")
      '\n' => sb.write_string("\\n")
      '\r' => sb.write_string("\\r")
      '\t' => sb.write_string("\\t")
      '\u{08}' => sb.write_string("\\b")
      '\u{0c}' => sb.write_string("\\f")
      _ =>
        if c.to_int() < 0x20 {
          let h = c.to_int()
          let hex = "0123456789abcdef"
          sb.write_string("\\u00")
          sb.write_char(hex.get_char(h / 16).unwrap())
          sb.write_char(hex.get_char(h % 16).unwrap())
        } else {
          sb.write_char(c)
        }
    }
  }
  sb.write_char('"')
  sb.to_string()
}

///|
fn canonical_value(v : Value) -> String {
  match v {
    Node(e) => e.canonical()
    List(l) => "[" + l.map(canonical_value).join(",") + "]"
    Bool(b) => if b { "true" } else { "false" }
    DT(d) => "DType." + d.name()
    Int(i) => i.to_string()
    Str(s) => json_quote(s)
  }
}

///|
/// Canonical dump: `Kind(k=v,...)` with keys in arg_types order.
pub fn Expr::canonical(self : Expr) -> String {
  let order = self.kind.arg_types()
  let keys = []
  for kv in order {
    if self.args.contains(kv.0) {
      keys.push(kv.0)
    }
  }
  let extra = []
  for k, _ in self.args {
    if !self.kind.has_arg(k) {
      extra.push(k)
    }
  }
  extra.sort()
  let parts = []
  for k in keys + extra {
    let v = self.args[k]
    match v {
      List(l) if l.is_empty() => continue
      _ => ()
    }
    parts.push(k + "=" + canonical_value(v))
  }
  match self.comments {
    Some(c) if !c.is_empty() =>
      parts.push("#c=[" + c.map(json_quote).join(", ") + "]")
    _ => ()
  }
  self.kind.name() + "(" + parts.join(",") + ")"
}