///|
/// A value that a template can use: an argument or a context value.
pub(all) enum TemplateValue {
  Null
  Bool(Bool)
  Int(Int32)
  Text(String)
  Sym(Symbol)
  Ref(SymbolReference)
  Call((CodeWriter) -> Unit raise CodegenError)
  List(Array[TemplateValue])
  Record(Map[String, TemplateValue])
}

///|
/// False for `Null`, `Bool(false)`, empty text, an empty list and an empty
/// record. True for every other value, also for `Int(0)`.
pub fn TemplateValue::is_truthy(self : TemplateValue) -> Bool {
  match self {
    Null => false
    Bool(value) => value
    Text(text) => !text.is_empty()
    List(items) => !items.is_empty()
    Record(fields) => !fields.is_empty()
    Int(_) | Sym(_) | Ref(_) | Call(_) => true
  }
}

///|
/// The text as a JSON string literal. I escape `"`, `\`, newline, carriage
/// return and tab. I write other code points below 0x20 as `\u00XX`. I keep
/// all other characters as they are.
pub fn json_string_literal(text : String) -> String {
  let out = StringBuilder()
  out.write_char('"')
  for c in text {
    match c {
      '"' => out.write_string("\\\"")
      '\\' => out.write_string("\\\\")
      '\n' => out.write_string("\\n")
      '\r' => out.write_string("\\r")
      '\t' => out.write_string("\\t")
      _ =>
        if c.to_int() < 0x20 {
          out.write_string("\\u00")
          out.write_char(hex_digit(c.to_int() / 16))
          out.write_char(hex_digit(c.to_int() % 16))
        } else {
          out.write_char(c)
        }
    }
  }
  out.write_char('"')
  out.to_string()
}

///|
fn hex_digit(value : Int32) -> Char {
  "0123456789abcdef"[value].to_int().unsafe_to_char()
}