use std::fmt;
use crate::value::Value;
fn receiver_label(v: &Value) -> String {
match v {
Value::Object(o) => o.borrow().class_name.to_string(),
other => other.type_name().to_string(),
}
}
fn repr(v: &Value) -> String {
match v {
Value::Str(s) => format!("\"{s}\""),
other => other.to_string(),
}
}
fn bytes_repr(bytes: &[u8]) -> String {
let mut out = String::from("b\"");
for &byte in bytes {
match byte {
b'"' => out.push_str("\\\""),
b'\\' => out.push_str("\\\\"),
0x20..=0x7e => out.push(byte as char),
_ => out.push_str(&format!("\\x{byte:02x}")),
}
}
out.push('"');
out
}
impl fmt::Display for Value {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Value::Int(n) => write!(f, "{n}"),
Value::Float(x) => {
if x.is_finite() && x.fract() == 0.0 {
write!(f, "{x:.1}")
} else {
write!(f, "{x}")
}
}
Value::Decimal(d) => write!(f, "{d}"),
Value::Str(s) => write!(f, "{s}"),
Value::Bytes(b) => write!(f, "{}", bytes_repr(b)),
Value::Bool(b) => write!(f, "{b}"),
Value::None => write!(f, "none"),
Value::List(items) => {
let items = items.borrow();
let inner = items.iter().map(repr).collect::<Vec<_>>().join(", ");
write!(f, "[{inner}]")
}
Value::Dict(entries) => {
let entries = entries.borrow();
let inner = entries
.iter()
.map(|(k, v)| format!("\"{k}\": {}", repr(v)))
.collect::<Vec<_>>()
.join(", ");
write!(f, "{{{inner}}}")
}
Value::Object(o) => write!(f, "<{}>", o.borrow().class_name),
Value::Function(func) => write!(f, "<function {}>", func.name),
Value::Class(class) => write!(f, "<class {}>", class.name),
Value::BoundMethod(m) => {
write!(f, "<method {}.{}>", receiver_label(&m.receiver), m.method)
}
Value::Error(e) => write!(f, "{}", e.message),
Value::Socket(_) => write!(f, "<socket>"),
Value::Pup(_) => write!(f, "<pup>"),
Value::Bowl(_) => write!(f, "<bowl>"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn scalars_format_python_style() {
assert_eq!(Value::int(7).to_string(), "7");
assert_eq!(Value::Float(2.5).to_string(), "2.5");
assert_eq!(Value::Float(3.0).to_string(), "3.0");
assert_eq!(Value::str("kabosu").to_string(), "kabosu");
assert_eq!(Value::Bool(true).to_string(), "true");
assert_eq!(Value::None.to_string(), "none");
}
#[test]
fn decimals_print_at_their_own_scale() {
use std::str::FromStr;
let d = Value::decimal(bigdecimal::BigDecimal::from_str("0.10").unwrap());
assert_eq!(d.to_string(), "0.10");
assert_eq!(Value::list(vec![d]).to_string(), "[0.10]");
}
#[test]
fn strings_are_bare_at_top_level_but_quoted_when_nested() {
assert_eq!(Value::str("wow").to_string(), "wow");
let list = Value::list(vec![Value::str("a"), Value::int(1)]);
assert_eq!(list.to_string(), "[\"a\", 1]");
}
#[test]
fn dict_formats_with_quoted_keys_in_insertion_order() {
let mut map = crate::ordered_map::OrderedMap::new();
map.insert("name".to_string(), Value::str("kabosu"));
map.insert("age".to_string(), Value::int(7));
assert_eq!(
Value::dict(map).to_string(),
"{\"name\": \"kabosu\", \"age\": 7}"
);
}
#[test]
fn object_prints_its_class_in_angle_brackets() {
assert_eq!(Value::object(0, "Shibe").to_string(), "<Shibe>");
}
#[test]
fn function_prints_its_name_in_angle_brackets() {
assert_eq!(
Value::function(0, "greet", vec![]).to_string(),
"<function greet>"
);
}
#[test]
fn bound_method_prints_its_receiver_and_name() {
let obj = Value::object(0, "Shibe");
assert_eq!(
Value::bound_method(obj, "speak").to_string(),
"<method Shibe.speak>"
);
let list = Value::list(vec![]);
assert_eq!(
Value::bound_method(list, "append").to_string(),
"<method List.append>"
);
}
#[test]
fn bytes_print_in_b_quote_form_with_hex_escapes() {
assert_eq!(Value::bytes("hi").to_string(), "b\"hi\"");
assert_eq!(
Value::bytes([0x00, 0xff, b'"', b'\\']).to_string(),
"b\"\\x00\\xff\\\"\\\\\""
);
assert_eq!(
Value::list(vec![Value::bytes("hi")]).to_string(),
"[b\"hi\"]"
);
}
#[test]
fn error_prints_its_message() {
let err = crate::error::error_value(
&crate::error::DogeError::type_error("much wrong"),
"s.doge",
2,
);
assert_eq!(err.to_string(), "much wrong");
assert_eq!(Value::list(vec![err]).to_string(), "[much wrong]");
}
}