// Source round-trip (JS per-class toString): every Val prints back as its
// original syntax. Implements the Show declare in spec.mbt.

///|
fn val_source(val : Val) -> String {
  match val {
    Const(lit~, ..) =>
      match lit {
        LStr(s) => escape_str_literal(s)
        LNull => "null"
        LBool(b) => if b { "true" } else { "false" }
        LNum(n) => num_source(n)
      }
    // JS StrTplVal has no toString of its own; reconstructing the `$'…'`
    // source is strictly more useful and keeps macro-attr round-trips sane.
    StrTpl(parts) => {
      let buf = StringBuilder::new()
      buf.write_string("$'")
      for part in parts {
        match part {
          Some(Const(lit=LStr(s), ..)) =>
            // Re-escape the raw text (the parse unescaped it once).
            escape_str_into(buf, s)
          Some(v) => {
            buf.write_char('{')
            buf.write_string(val_source(v))
            buf.write_char('}')
          }
          None => buf.write_string("{}")
        }
      }
      buf.write_char('\'')
      buf.to_string()
    }
    App(name~, args~) =>
      // A zero-argument application prints as the bare name, so the round trip
      // holds once the vocabulary admits one.
      if args.is_empty() {
        name
      } else {
        name + " " + args.map(a => val_source(a)).join(" ")
      }
    Name(name) => name
    HandlerName(name~, ..) => name
    TypeName(name) => name
    Bind(name) => "@" + name
    BindMember(name~, prop~) => "@" + name + "." + prop
    Dyn(name) => "*" + name
    Field(name) => "." + name
    Method(name) => "$" + name
    SeqAccess(seq~, key~) => "." + seq + "[." + key + "]"
  }
}

///|
pub impl Show for Val with fn output(self, logger) {
  logger.write_string(val_source(self))
}