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        }
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68
69    #[test]
70    fn scalars_format_python_style() {
71        assert_eq!(Value::Int(7).to_string(), "7");
72        assert_eq!(Value::Float(2.5).to_string(), "2.5");
73        assert_eq!(Value::Float(3.0).to_string(), "3.0");
74        assert_eq!(Value::str("kabosu").to_string(), "kabosu");
75        assert_eq!(Value::Bool(true).to_string(), "true");
76        assert_eq!(Value::None.to_string(), "none");
77    }
78
79    #[test]
80    fn strings_are_bare_at_top_level_but_quoted_when_nested() {
81        assert_eq!(Value::str("wow").to_string(), "wow");
82        let list = Value::list(vec![Value::str("a"), Value::Int(1)]);
83        assert_eq!(list.to_string(), "[\"a\", 1]");
84    }
85
86    #[test]
87    fn dict_formats_with_quoted_keys_in_insertion_order() {
88        let mut map = crate::ordered_map::OrderedMap::new();
89        map.insert("name".to_string(), Value::str("kabosu"));
90        map.insert("age".to_string(), Value::Int(7));
91        assert_eq!(
92            Value::dict(map).to_string(),
93            "{\"name\": \"kabosu\", \"age\": 7}"
94        );
95    }
96
97    #[test]
98    fn object_prints_its_class_in_angle_brackets() {
99        assert_eq!(Value::object(0, "Shibe").to_string(), "<Shibe>");
100    }
101
102    #[test]
103    fn function_prints_its_name_in_angle_brackets() {
104        assert_eq!(
105            Value::function(0, "greet", vec![]).to_string(),
106            "<function greet>"
107        );
108    }
109
110    #[test]
111    fn bound_method_prints_its_receiver_and_name() {
112        let obj = Value::object(0, "Shibe");
113        assert_eq!(
114            Value::bound_method(obj, "speak").to_string(),
115            "<method Shibe.speak>"
116        );
117        let list = Value::list(vec![]);
118        assert_eq!(
119            Value::bound_method(list, "append").to_string(),
120            "<method List.append>"
121        );
122    }
123
124    #[test]
125    fn error_prints_its_message() {
126        let err = crate::error::error_value(
127            &crate::error::DogeError::type_error("much wrong"),
128            "s.doge",
129            2,
130        );
131        assert_eq!(err.to_string(), "much wrong");
132        // Nested in a container it stays bare (it is not a Str), like objects.
133        assert_eq!(Value::list(vec![err]).to_string(), "[much wrong]");
134    }
135}