1use std::fmt;
2
3use crate::value::Value;
4
5fn receiver_label(v: &Value) -> String {
9 match v {
10 Value::Object(o) => o.borrow().class_name.to_string(),
11 other => other.type_name().to_string(),
12 }
13}
14
15fn repr(v: &Value) -> String {
19 match v {
20 Value::Str(s) => format!("\"{s}\""),
21 other => other.to_string(),
22 }
23}
24
25fn bytes_repr(bytes: &[u8]) -> String {
29 let mut out = String::from("b\"");
30 for &byte in bytes {
31 match byte {
32 b'"' => out.push_str("\\\""),
33 b'\\' => out.push_str("\\\\"),
34 0x20..=0x7e => out.push(byte as char),
35 _ => out.push_str(&format!("\\x{byte:02x}")),
36 }
37 }
38 out.push('"');
39 out
40}
41
42impl fmt::Display for Value {
43 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44 match self {
45 Value::Int(n) => write!(f, "{n}"),
46 Value::Float(x) => {
47 if x.is_finite() && x.fract() == 0.0 {
50 write!(f, "{x:.1}")
51 } else {
52 write!(f, "{x}")
53 }
54 }
55 Value::Decimal(d) => write!(f, "{d}"),
58 Value::Str(s) => write!(f, "{s}"),
59 Value::Bytes(b) => write!(f, "{}", bytes_repr(b)),
60 Value::Bool(b) => write!(f, "{b}"),
61 Value::None => write!(f, "none"),
62 Value::List(items) => {
63 let items = items.borrow();
64 let inner = items.iter().map(repr).collect::<Vec<_>>().join(", ");
65 write!(f, "[{inner}]")
66 }
67 Value::Dict(entries) => {
68 let entries = entries.borrow();
69 let inner = entries
70 .iter()
71 .map(|(k, v)| format!("\"{k}\": {}", repr(v)))
72 .collect::<Vec<_>>()
73 .join(", ");
74 write!(f, "{{{inner}}}")
75 }
76 Value::Object(o) => write!(f, "<{}>", o.borrow().class_name),
77 Value::Function(func) => write!(f, "<function {}>", func.name),
78 Value::Class(class) => write!(f, "<class {}>", class.name),
79 Value::BoundMethod(m) => {
80 write!(f, "<method {}.{}>", receiver_label(&m.receiver), m.method)
81 }
82 Value::Error(e) => write!(f, "{}", e.message),
83 Value::Socket(_) => write!(f, "<socket>"),
84 Value::Pup(_) => write!(f, "<pup>"),
85 Value::Bowl(_) => write!(f, "<bowl>"),
86 }
87 }
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93
94 #[test]
95 fn scalars_format_python_style() {
96 assert_eq!(Value::int(7).to_string(), "7");
97 assert_eq!(Value::Float(2.5).to_string(), "2.5");
98 assert_eq!(Value::Float(3.0).to_string(), "3.0");
99 assert_eq!(Value::str("kabosu").to_string(), "kabosu");
100 assert_eq!(Value::Bool(true).to_string(), "true");
101 assert_eq!(Value::None.to_string(), "none");
102 }
103
104 #[test]
105 fn decimals_print_at_their_own_scale() {
106 use std::str::FromStr;
107 let d = Value::decimal(bigdecimal::BigDecimal::from_str("0.10").unwrap());
109 assert_eq!(d.to_string(), "0.10");
110 assert_eq!(Value::list(vec![d]).to_string(), "[0.10]");
112 }
113
114 #[test]
115 fn strings_are_bare_at_top_level_but_quoted_when_nested() {
116 assert_eq!(Value::str("wow").to_string(), "wow");
117 let list = Value::list(vec![Value::str("a"), Value::int(1)]);
118 assert_eq!(list.to_string(), "[\"a\", 1]");
119 }
120
121 #[test]
122 fn dict_formats_with_quoted_keys_in_insertion_order() {
123 let mut map = crate::ordered_map::OrderedMap::new();
124 map.insert("name".to_string(), Value::str("kabosu"));
125 map.insert("age".to_string(), Value::int(7));
126 assert_eq!(
127 Value::dict(map).to_string(),
128 "{\"name\": \"kabosu\", \"age\": 7}"
129 );
130 }
131
132 #[test]
133 fn object_prints_its_class_in_angle_brackets() {
134 assert_eq!(Value::object(0, "Shibe").to_string(), "<Shibe>");
135 }
136
137 #[test]
138 fn function_prints_its_name_in_angle_brackets() {
139 assert_eq!(
140 Value::function(0, "greet", vec![]).to_string(),
141 "<function greet>"
142 );
143 }
144
145 #[test]
146 fn bound_method_prints_its_receiver_and_name() {
147 let obj = Value::object(0, "Shibe");
148 assert_eq!(
149 Value::bound_method(obj, "speak").to_string(),
150 "<method Shibe.speak>"
151 );
152 let list = Value::list(vec![]);
153 assert_eq!(
154 Value::bound_method(list, "append").to_string(),
155 "<method List.append>"
156 );
157 }
158
159 #[test]
160 fn bytes_print_in_b_quote_form_with_hex_escapes() {
161 assert_eq!(Value::bytes("hi").to_string(), "b\"hi\"");
162 assert_eq!(
164 Value::bytes([0x00, 0xff, b'"', b'\\']).to_string(),
165 "b\"\\x00\\xff\\\"\\\\\""
166 );
167 assert_eq!(
169 Value::list(vec![Value::bytes("hi")]).to_string(),
170 "[b\"hi\"]"
171 );
172 }
173
174 #[test]
175 fn error_prints_its_message() {
176 let err = crate::error::error_value(
177 &crate::error::DogeError::type_error("much wrong"),
178 "s.doge",
179 2,
180 );
181 assert_eq!(err.to_string(), "much wrong");
182 assert_eq!(Value::list(vec![err]).to_string(), "[much wrong]");
184 }
185}