spore_vm/val/
formatter.rs

1use crate::Vm;
2
3use super::UnsafeVal;
4
5/// Formats a value for display.
6pub struct ValFormatter<'a> {
7    vm: &'a Vm,
8    val: UnsafeVal,
9    quote_strings: bool,
10}
11
12impl<'a> ValFormatter<'a> {
13    /// Create a new value formatter that implements display.
14    pub fn new(vm: &'a Vm, v: UnsafeVal) -> ValFormatter {
15        ValFormatter {
16            vm,
17            val: v,
18            quote_strings: false,
19        }
20    }
21
22    /// Create a new value formatter that implements display. Strings are printed in quotes. For
23    /// example, a string containing the string test-string will print to "test-string".
24    pub fn new_quoted(vm: &'a Vm, v: UnsafeVal) -> ValFormatter {
25        ValFormatter {
26            vm,
27            val: v,
28            quote_strings: true,
29        }
30    }
31}
32
33impl<'a> std::fmt::Display for ValFormatter<'a> {
34    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35        match &self.val {
36            UnsafeVal::Void => write!(f, "<void>"),
37            UnsafeVal::Bool(x) => write!(f, "{x}"),
38            UnsafeVal::Int(x) => write!(f, "{x}"),
39            UnsafeVal::Float(x) => write!(f, "{x}"),
40            UnsafeVal::Symbol(x) => {
41                let name = self.vm.symbol_to_str(*x).unwrap_or("*corrupt-symbol*");
42                write!(f, "'{name}")
43            }
44            UnsafeVal::String(x) => {
45                if self.quote_strings {
46                    write!(f, "{:?}", self.vm.objects.get_str(*x))
47                } else {
48                    write!(f, "{}", self.vm.objects.get_str(*x))
49                }
50            }
51            UnsafeVal::MutableBox(x) => {
52                let inner = ValFormatter {
53                    vm: self.vm,
54                    val: *self.vm.objects.get_mutable_box(*x),
55                    quote_strings: true,
56                };
57                write!(f, "box<{}>", inner)
58            }
59            UnsafeVal::List(x) => {
60                write!(f, "(")?;
61                for (idx, val) in self.vm.objects.get_list(*x).iter().enumerate() {
62                    if idx == 0 {
63                        write!(f, "{}", val.format_quoted(self.vm))?;
64                    } else {
65                        write!(f, " {}", val.format_quoted(self.vm))?;
66                    }
67                }
68                write!(f, ")")
69            }
70            UnsafeVal::Struct(x) => {
71                write!(f, "(struct")?;
72                for (name, val) in self.vm.objects.get_struct(*x).iter() {
73                    let val = val.format_quoted(self.vm);
74                    let name = self
75                        .vm
76                        .symbol_to_str(name)
77                        .unwrap_or("*unknown-symbol-name*");
78                    write!(f, " '{name} {val}")?;
79                }
80                write!(f, ")")
81            }
82            UnsafeVal::ByteCodeFunction(bc) => {
83                let bc = self.vm.objects.get_bytecode(*bc).unwrap();
84                write!(
85                    f,
86                    "<function {name}>",
87                    name = if bc.name.is_empty() {
88                        "_"
89                    } else {
90                        bc.name.as_str()
91                    }
92                )
93            }
94            UnsafeVal::NativeFunction(_) => write!(f, "<native-function>"),
95            UnsafeVal::Custom(c) => {
96                let c = self.vm.objects.get_custom(*c);
97                write!(f, "{c}")
98            }
99        }
100    }
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106
107    #[test]
108    fn format_void_is_empty() {
109        assert_eq!(
110            UnsafeVal::from(()).formatted(&Vm::default()).to_string(),
111            "<void>"
112        );
113    }
114
115    #[test]
116    fn format_bool_is_true_or_false() {
117        assert_eq!(
118            UnsafeVal::from(true).formatted(&Vm::default()).to_string(),
119            "true"
120        );
121        assert_eq!(
122            UnsafeVal::from(false).formatted(&Vm::default()).to_string(),
123            "false"
124        );
125    }
126
127    #[test]
128    fn format_int_prints_number() {
129        assert_eq!(
130            UnsafeVal::from(0).formatted(&Vm::default()).to_string(),
131            "0"
132        );
133        assert_eq!(
134            UnsafeVal::from(-1).formatted(&Vm::default()).to_string(),
135            "-1"
136        );
137    }
138
139    #[test]
140    fn format_float_prints_number() {
141        assert_eq!(
142            UnsafeVal::from(0.0).formatted(&Vm::default()).to_string(),
143            "0"
144        );
145        assert_eq!(
146            UnsafeVal::from(-1.5).formatted(&Vm::default()).to_string(),
147            "-1.5"
148        );
149    }
150
151    #[test]
152    fn format_string_produces_string() {
153        let mut vm = Vm::default();
154        let string_id = vm.objects.insert_string("my string".into());
155        assert_eq!(
156            UnsafeVal::from(string_id).formatted(&vm).to_string(),
157            "my string"
158        );
159    }
160
161    #[test]
162    fn format_struct_produces_all_key_values() {
163        let mut vm = Vm::default();
164        vm.eval_str("(define x (struct 'field-1 1 'field-2 \"2\"))")
165            .unwrap();
166        let res = vm.eval_str("x").unwrap();
167        assert!(
168            res.formatted(res.vm()).to_string() == "(struct 'field-1 1 'field-2 \"2\")"
169                || res.formatted(res.vm()).to_string() == "(struct 'field-2 \"2\" 'field-1 1)",
170            "{}",
171            res.formatted(res.vm()).to_string()
172        );
173    }
174
175    #[test]
176    fn format_mutable_box_produces_underlying_value() {
177        let mut vm = Vm::default();
178        vm.eval_str("(define x (new-box \"string\"))").unwrap();
179        let res = vm.eval_str("x").unwrap();
180        assert!(res.get_mutable_box_ref().is_ok());
181        assert_eq!(res.formatted(res.vm()).to_string(), "box<\"string\">");
182    }
183
184    #[test]
185    fn format_function_prints_name() {
186        let mut vm = Vm::default();
187        let v = vm.eval_str("(define (foo) 10) foo").unwrap();
188        assert_eq!(v.to_string(), "<function foo>");
189    }
190
191    #[test]
192    fn format_native_function_prints_native_function() {
193        let mut vm = Vm::default();
194        let v = vm.eval_str("+").unwrap();
195        assert_eq!(v.to_string(), "<native-function>");
196    }
197}