Skip to main content

doge_runtime/
display.rs

1use std::fmt;
2
3use crate::value::Value;
4
5/// The receiver's name in a bound method's display form: a `many` instance shows
6/// its class (`<method Shibe.speak>`), a collection its type (`<method
7/// List.append>`).
8fn 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
15/// String form of a value as it appears *nested* inside a container: strings
16/// gain quotes, everything else prints as it would on its own.
17fn repr(v: &Value) -> String {
18    match v {
19        Value::Str(s) => format!("\"{s}\""),
20        other => other.to_string(),
21    }
22}
23
24impl fmt::Display for Value {
25    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26        match self {
27            Value::Int(n) => write!(f, "{n}"),
28            Value::Float(x) => {
29                // Always show a decimal point so Floats never look like Ints:
30                // 3.0 prints "3.0", 2.5 prints "2.5".
31                if x.is_finite() && x.fract() == 0.0 {
32                    write!(f, "{x:.1}")
33                } else {
34                    write!(f, "{x}")
35                }
36            }
37            Value::Str(s) => write!(f, "{s}"),
38            Value::Bool(b) => write!(f, "{b}"),
39            Value::None => write!(f, "none"),
40            Value::List(items) => {
41                let items = items.borrow();
42                let inner = items.iter().map(repr).collect::<Vec<_>>().join(", ");
43                write!(f, "[{inner}]")
44            }
45            Value::Dict(entries) => {
46                let entries = entries.borrow();
47                let inner = entries
48                    .iter()
49                    .map(|(k, v)| format!("\"{k}\": {}", repr(v)))
50                    .collect::<Vec<_>>()
51                    .join(", ");
52                write!(f, "{{{inner}}}")
53            }
54            Value::Object(o) => write!(f, "<{}>", o.borrow().class_name),
55            Value::Function(func) => write!(f, "<function {}>", func.name),
56            Value::Class(class) => write!(f, "<class {}>", class.name),
57            Value::BoundMethod(m) => {
58                write!(f, "<method {}.{}>", receiver_label(&m.receiver), m.method)
59            }
60            Value::Error(e) => write!(f, "{}", e.message),
61            Value::Socket(_) => write!(f, "<socket>"),
62            Value::Pup(_) => write!(f, "<pup>"),
63            Value::Bowl(_) => write!(f, "<bowl>"),
64        }
65    }
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71
72    #[test]
73    fn scalars_format_python_style() {
74        assert_eq!(Value::Int(7).to_string(), "7");
75        assert_eq!(Value::Float(2.5).to_string(), "2.5");
76        assert_eq!(Value::Float(3.0).to_string(), "3.0");
77        assert_eq!(Value::str("kabosu").to_string(), "kabosu");
78        assert_eq!(Value::Bool(true).to_string(), "true");
79        assert_eq!(Value::None.to_string(), "none");
80    }
81
82    #[test]
83    fn strings_are_bare_at_top_level_but_quoted_when_nested() {
84        assert_eq!(Value::str("wow").to_string(), "wow");
85        let list = Value::list(vec![Value::str("a"), Value::Int(1)]);
86        assert_eq!(list.to_string(), "[\"a\", 1]");
87    }
88
89    #[test]
90    fn dict_formats_with_quoted_keys_in_insertion_order() {
91        let mut map = crate::ordered_map::OrderedMap::new();
92        map.insert("name".to_string(), Value::str("kabosu"));
93        map.insert("age".to_string(), Value::Int(7));
94        assert_eq!(
95            Value::dict(map).to_string(),
96            "{\"name\": \"kabosu\", \"age\": 7}"
97        );
98    }
99
100    #[test]
101    fn object_prints_its_class_in_angle_brackets() {
102        assert_eq!(Value::object(0, "Shibe").to_string(), "<Shibe>");
103    }
104
105    #[test]
106    fn function_prints_its_name_in_angle_brackets() {
107        assert_eq!(
108            Value::function(0, "greet", vec![]).to_string(),
109            "<function greet>"
110        );
111    }
112
113    #[test]
114    fn bound_method_prints_its_receiver_and_name() {
115        let obj = Value::object(0, "Shibe");
116        assert_eq!(
117            Value::bound_method(obj, "speak").to_string(),
118            "<method Shibe.speak>"
119        );
120        let list = Value::list(vec![]);
121        assert_eq!(
122            Value::bound_method(list, "append").to_string(),
123            "<method List.append>"
124        );
125    }
126
127    #[test]
128    fn error_prints_its_message() {
129        let err = crate::error::error_value(
130            &crate::error::DogeError::type_error("much wrong"),
131            "s.doge",
132            2,
133        );
134        assert_eq!(err.to_string(), "much wrong");
135        // Nested in a container it stays bare (it is not a Str), like objects.
136        assert_eq!(Value::list(vec![err]).to_string(), "[much wrong]");
137    }
138}