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 {
49 write!(f, "{x:.1}")
50 } else {
51 write!(f, "{x}")
52 }
53 }
54 Value::Decimal(d) => write!(f, "{d}"),
55 Value::Str(s) => write!(f, "{s}"),
56 Value::Bytes(b) => write!(f, "{}", bytes_repr(b)),
57 Value::Bool(b) => write!(f, "{b}"),
58 Value::None => write!(f, "none"),
59 Value::List(items) => {
60 let items = items.borrow();
61 let inner = items.iter().map(repr).collect::<Vec<_>>().join(", ");
62 write!(f, "[{inner}]")
63 }
64 Value::Dict(entries) => {
65 let entries = entries.borrow();
66 let inner = entries
67 .iter()
68 .map(|(k, v)| format!("\"{k}\": {}", repr(v)))
69 .collect::<Vec<_>>()
70 .join(", ");
71 write!(f, "{{{inner}}}")
72 }
73 Value::Object(o) => write!(f, "<{}>", o.borrow().class_name),
74 Value::Function(func) => write!(f, "<function {}>", func.name),
75 Value::Class(class) => write!(f, "<class {}>", class.name),
76 Value::BoundMethod(m) => {
77 write!(f, "<method {}.{}>", receiver_label(&m.receiver), m.method)
78 }
79 Value::Error(e) => write!(f, "{}", e.message),
80 Value::Socket(_) => write!(f, "<socket>"),
81 Value::Pup(_) => write!(f, "<pup>"),
82 Value::Bowl(_) => write!(f, "<bowl>"),
83 }
84 }
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90
91 #[test]
92 fn scalars_format_python_style() {
93 assert_eq!(Value::int(7).to_string(), "7");
94 assert_eq!(Value::Float(2.5).to_string(), "2.5");
95 assert_eq!(Value::Float(3.0).to_string(), "3.0");
96 assert_eq!(Value::str("kabosu").to_string(), "kabosu");
97 assert_eq!(Value::Bool(true).to_string(), "true");
98 assert_eq!(Value::None.to_string(), "none");
99 }
100
101 #[test]
102 fn decimals_print_at_their_own_scale() {
103 use std::str::FromStr;
104 let d = Value::decimal(bigdecimal::BigDecimal::from_str("0.10").unwrap());
106 assert_eq!(d.to_string(), "0.10");
107 assert_eq!(Value::list(vec![d]).to_string(), "[0.10]");
109 }
110
111 #[test]
112 fn strings_are_bare_at_top_level_but_quoted_when_nested() {
113 assert_eq!(Value::str("wow").to_string(), "wow");
114 let list = Value::list(vec![Value::str("a"), Value::int(1)]);
115 assert_eq!(list.to_string(), "[\"a\", 1]");
116 }
117
118 #[test]
119 fn dict_formats_with_quoted_keys_in_insertion_order() {
120 let mut map = crate::ordered_map::OrderedMap::new();
121 map.insert("name".to_string(), Value::str("kabosu"));
122 map.insert("age".to_string(), Value::int(7));
123 assert_eq!(
124 Value::dict(map).to_string(),
125 "{\"name\": \"kabosu\", \"age\": 7}"
126 );
127 }
128
129 #[test]
130 fn object_prints_its_class_in_angle_brackets() {
131 assert_eq!(Value::object(0, "Shibe").to_string(), "<Shibe>");
132 }
133
134 #[test]
135 fn function_prints_its_name_in_angle_brackets() {
136 assert_eq!(
137 Value::function(0, "greet", vec![]).to_string(),
138 "<function greet>"
139 );
140 }
141
142 #[test]
143 fn bound_method_prints_its_receiver_and_name() {
144 let obj = Value::object(0, "Shibe");
145 assert_eq!(
146 Value::bound_method(obj, "speak").to_string(),
147 "<method Shibe.speak>"
148 );
149 let list = Value::list(vec![]);
150 assert_eq!(
151 Value::bound_method(list, "append").to_string(),
152 "<method List.append>"
153 );
154 }
155
156 #[test]
157 fn bytes_print_in_b_quote_form_with_hex_escapes() {
158 assert_eq!(Value::bytes("hi").to_string(), "b\"hi\"");
159 assert_eq!(
161 Value::bytes([0x00, 0xff, b'"', b'\\']).to_string(),
162 "b\"\\x00\\xff\\\"\\\\\""
163 );
164 assert_eq!(
166 Value::list(vec![Value::bytes("hi")]).to_string(),
167 "[b\"hi\"]"
168 );
169 }
170
171 #[test]
172 fn error_prints_its_message() {
173 let err = crate::error::error_value(
174 &crate::error::DogeError::type_error("much wrong"),
175 "s.doge",
176 2,
177 );
178 assert_eq!(err.to_string(), "much wrong");
179 assert_eq!(Value::list(vec![err]).to_string(), "[much wrong]");
181 }
182}