kamo/value/
printer.rs

1use std::{collections::HashSet, fmt};
2use unicode_ident::{is_xid_continue, is_xid_start};
3
4#[cfg(feature = "types")]
5use crate::types::Type;
6
7use super::{Pair, Value, Vector, Visitor};
8
9/// This function prints a value on a single line. It formats the value in a
10/// Scheme-like syntax.
11///
12/// The returned [`SimplePrinter`](SimplePrinter) implements the
13/// [`Display`](std::fmt::Display) trait and can be used to print the value.
14///
15/// # Example
16///
17/// ```rust
18/// use kamo::value::{print, Value};
19///
20/// let value = Value::new_int(42);
21/// let printer = print(value);
22///
23/// assert_eq!(printer.to_string(), "42");
24/// ```
25#[must_use]
26#[inline]
27pub const fn print(value: Value<'_>) -> SimplePrinter<'_> {
28    SimplePrinter(value)
29}
30
31/// A wrapper around a [`Value`](super::Value) that implements the
32/// [`Display`](std::fmt::Display) trait. It can be used to print a value in a
33/// Scheme-like syntax. The output is a single line.
34///
35/// # Example
36///
37/// ```rust
38/// use kamo::value::{SimplePrinter, Value};
39///
40/// let value = Value::new_int(42);
41/// let printer = SimplePrinter(value);
42///
43/// assert_eq!(printer.to_string(), "42");
44/// ```
45#[derive(Debug)]
46pub struct SimplePrinter<'a>(pub Value<'a>);
47
48impl<'a> fmt::Display for SimplePrinter<'a> {
49    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50        let mut visitor = SimplePrinterVisitor(f);
51        self.0.accept(&mut visitor)
52    }
53}
54
55/// A visitor that prints a value in a Scheme-like syntax to the given
56/// [`Formatter`](std::fmt::Formatter).
57///
58/// It is used by the [`SimplePrinter`](SimplePrinter) to print a value. The
59/// value is printed on a single line.
60///
61/// # Note
62///
63/// The implementation of the visit-methods considers that the value may be
64/// circular. In order to prevent infinite recursion, the implementation checks
65/// if the value, either a list or vector, has already been visited and if so,
66/// returns early. Circular lists are printed as `<cyclic list>` and circular
67/// vectors as `<cyclic vector>`.
68///
69/// # Example
70///
71/// ```rust
72/// use kamo::{
73///     mem::Mutator,
74///     value::{SimplePrinterVisitor, Value}
75/// };
76///
77/// // A wrapper around a `Value` that implements `Display`.
78/// struct SimplePrinter<'a>(pub Value<'a>);
79///
80/// impl<'a> std::fmt::Display for SimplePrinter<'a> {
81///     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
82///         // Create a visitor that prints the value.
83///         let mut visitor = SimplePrinterVisitor(f);
84///         // Accept the visitor.
85///         self.0.accept(&mut visitor)
86///     }
87/// }
88///
89/// // Display a simple value.
90/// let value = Value::new_int(42);
91/// let printer = SimplePrinter(value);
92///
93/// assert_eq!(printer.to_string(), "42");
94///
95/// // Create a `Mutator` to allocate values.
96/// let m = Mutator::new_ref();
97///
98/// // Display a simple list.
99/// let value = Value::new_list(
100///     m.clone(),
101///     [Value::new_int(42), Value::new_int(43)],
102/// );
103/// let printer = SimplePrinter(value);
104///
105/// assert_eq!(printer.to_string(), "(42 43)");
106///
107/// // Display a cyclic list.
108/// let list = Value::new_list(
109///     m.clone(),
110///     [Value::new_int(42), Value::new_int(43), Value::new_int(44)],
111/// );
112/// // Make the list cyclic by setting the cdr of the last pair to the list.
113/// list.as_pair().unwrap()
114///     .cddr().unwrap()
115///     .as_pair_mut().unwrap()
116///     .set_cdr(list.clone());
117/// let printer = SimplePrinter(list);
118///
119/// assert_eq!(
120///     printer.to_string(),
121///     format!("(42 43 44 <cyclic list>)")
122/// );
123/// ```
124pub struct SimplePrinterVisitor<'a, 'b>(pub &'a mut fmt::Formatter<'b>);
125
126impl<'a, 'b> Visitor for SimplePrinterVisitor<'a, 'b> {
127    type Result = fmt::Result;
128
129    fn visit_nil(&mut self) -> Self::Result {
130        write!(self.0, "()")
131    }
132
133    fn visit_true(&mut self) -> Self::Result {
134        write!(self.0, "#t")
135    }
136
137    fn visit_false(&mut self) -> Self::Result {
138        write!(self.0, "#f")
139    }
140
141    fn visit_char(&mut self, value: char) -> Self::Result {
142        write!(self.0, "#\\")?;
143        if value.is_ascii() {
144            match value {
145                '\x07' => write!(self.0, "alarm"),
146                '\x08' => write!(self.0, "backspace"),
147                '\x7f' => write!(self.0, "delete"),
148                '\x1b' => write!(self.0, "escape"),
149                '\n' => write!(self.0, "newline"),
150                '\0' => write!(self.0, "null"),
151                '\r' => write!(self.0, "return"),
152                ' ' => write!(self.0, "space"),
153                '\t' => write!(self.0, "tab"),
154                value if value.is_ascii_control() => write!(self.0, "x{:02x}", value as u32),
155                value => write!(self.0, "{value}"),
156            }
157        } else if is_xid_start(value) || is_xid_continue(value) {
158            write!(self.0, "{value}")
159        } else {
160            write!(self.0, "u{{{:x}}}", value as u32)
161        }
162    }
163
164    fn visit_integer(&mut self, value: i64) -> Self::Result {
165        write!(self.0, "{value}")
166    }
167
168    fn visit_float(&mut self, value: f64) -> Self::Result {
169        if value.is_nan() {
170            write!(self.0, "+nan.0")
171        } else if value.is_infinite() {
172            write!(
173                self.0,
174                "{}inf.0",
175                if value.is_sign_negative() { "-" } else { "+" }
176            )
177        } else {
178            write!(self.0, "{value}")
179        }
180    }
181
182    fn visit_pair(&mut self, value: &Pair<'_>) -> Self::Result {
183        let mut seen = HashSet::new();
184        let mut tail = value.cdr();
185
186        write!(self.0, "(")?;
187        seen.insert(std::ptr::from_ref::<Pair<'_>>(value) as usize);
188        value.car().accept(self)?;
189        while let Some(value) = tail.as_pair() {
190            let ptr = std::ptr::from_ref::<Pair<'_>>(value) as usize;
191
192            if seen.contains(&ptr) {
193                return write!(self.0, " <cyclic list>)");
194            }
195            seen.insert(ptr);
196            write!(self.0, " ")?;
197            value.car().accept(self)?;
198            tail = value.cdr();
199        }
200        if !tail.is_nil() {
201            write!(self.0, " . ")?;
202            tail.accept(self)?;
203        }
204        write!(self.0, ")")
205    }
206
207    fn visit_string(&mut self, value: &str) -> Self::Result {
208        write!(self.0, "{value:?}")
209    }
210
211    fn visit_symbol(&mut self, value: &str) -> Self::Result {
212        write!(self.0, "{value}")
213    }
214
215    fn visit_bytevec(&mut self, value: &[u8]) -> Self::Result {
216        write!(self.0, "#u8(")?;
217        if let Some((first, rest)) = value.split_first() {
218            write!(self.0, "{first}")?;
219            for byte in rest {
220                write!(self.0, " {byte}")?;
221            }
222        }
223        write!(self.0, ")")
224    }
225
226    fn visit_vector(&mut self, value: &Vector<'_>) -> Self::Result {
227        let seen = std::ptr::from_ref::<Vector<'_>>(value) as usize;
228
229        write!(self.0, "#(")?;
230        if let Some((first, rest)) = value.split_first() {
231            let ptr = first
232                .as_vector()
233                .map_or(0, |v| std::ptr::from_ref::<Vector<'_>>(v) as usize);
234
235            if ptr == seen {
236                write!(self.0, "<cyclic vector>")?;
237            } else {
238                first.accept(self)?;
239            }
240            for value in rest {
241                let ptr = value
242                    .as_vector()
243                    .map_or(0, |v| std::ptr::from_ref::<Vector<'_>>(v) as usize);
244
245                if ptr == seen {
246                    write!(self.0, " <cyclic vector>")?;
247                } else {
248                    write!(self.0, " ")?;
249                    value.accept(self)?;
250                }
251            }
252        }
253        write!(self.0, ")")
254    }
255
256    #[cfg(feature = "types")]
257    #[cfg_attr(docsrs, doc(cfg(feature = "types")))]
258    fn visit_type(&mut self, value: &Type) -> Self::Result {
259        write!(self.0, "#<type {value}>")
260    }
261}
262
263#[cfg(test)]
264mod tests {
265    use crate::mem::Mutator;
266
267    use super::*;
268
269    #[allow(clippy::cognitive_complexity)]
270    #[test]
271    fn print_atoms() {
272        let m = Mutator::new_ref();
273
274        assert_eq!(print(Value::new_nil()).to_string(), "()");
275        assert_eq!(print(Value::new_bool(true)).to_string(), "#t");
276        assert_eq!(print(Value::new_bool(false)).to_string(), "#f");
277        assert_eq!(print(Value::new_int(42)).to_string(), "42");
278        assert_eq!(print(Value::new_float(42.0)).to_string(), "42");
279        assert_eq!(print(Value::new_float(42.5)).to_string(), "42.5");
280        assert_eq!(print(Value::new_char('a')).to_string(), "#\\a");
281        assert_eq!(print(Value::new_char(' ')).to_string(), "#\\space");
282        assert_eq!(print(Value::new_char('\n')).to_string(), "#\\newline");
283        assert_eq!(print(Value::new_char('\x07')).to_string(), "#\\alarm");
284        assert_eq!(print(Value::new_char('\x08')).to_string(), "#\\backspace");
285        assert_eq!(print(Value::new_char('\x7f')).to_string(), "#\\delete");
286        assert_eq!(print(Value::new_char('\x1b')).to_string(), "#\\escape");
287        assert_eq!(print(Value::new_char('\0')).to_string(), "#\\null");
288        assert_eq!(print(Value::new_char('\r')).to_string(), "#\\return");
289        assert_eq!(print(Value::new_char('\t')).to_string(), "#\\tab");
290        assert_eq!(print(Value::new_char('\x1f')).to_string(), "#\\x1f");
291        assert_eq!(print(Value::new_char('\u{80}')).to_string(), "#\\u{80}");
292        assert_eq!(
293            print(Value::new_symbol(m.clone(), "foo")).to_string(),
294            "foo"
295        );
296        assert_eq!(
297            print(Value::new_bytevec(m.clone(), [0, 1, 2])).to_string(),
298            "#u8(0 1 2)"
299        );
300        assert_eq!(print(Value::new_string(m.clone(), "")).to_string(), "\"\"");
301        assert_eq!(
302            print(Value::new_string(m.clone(), "foo")).to_string(),
303            "\"foo\""
304        );
305        assert_eq!(
306            print(Value::new_string(m.clone(), "foo\n")).to_string(),
307            "\"foo\\n\""
308        );
309        assert_eq!(
310            print(Value::new_string(m.clone(), "foo\"")).to_string(),
311            "\"foo\\\"\""
312        );
313        assert_eq!(
314            print(Value::new_string(m.clone(), "foo\\")).to_string(),
315            "\"foo\\\\\""
316        );
317        assert_eq!(
318            print(Value::new_string(m.clone(), "foo\t")).to_string(),
319            "\"foo\\t\""
320        );
321        assert_eq!(
322            print(Value::new_string(m.clone(), "foo\r")).to_string(),
323            "\"foo\\r\""
324        );
325        assert_eq!(
326            print(Value::new_string(m.clone(), "foo\x07")).to_string(),
327            "\"foo\\u{7}\""
328        );
329        assert_eq!(print(Value::new_vector(m.clone(), &[])).to_string(), "#()");
330        assert_eq!(
331            print(Value::new_vector(m.clone(), &[Value::new_int(42)])).to_string(),
332            "#(42)"
333        );
334        assert_eq!(
335            print(Value::new_vector(
336                m.clone(),
337                &[Value::new_int(42), Value::new_int(43)]
338            ))
339            .to_string(),
340            "#(42 43)"
341        );
342    }
343
344    #[test]
345    fn print_pairs() {
346        let m = Mutator::new_ref();
347
348        assert_eq!(
349            print(Value::new_cons(
350                m.clone(),
351                Value::new_int(42),
352                Value::new_nil()
353            ))
354            .to_string(),
355            "(42)"
356        );
357        assert_eq!(
358            print(Value::new_cons(
359                m.clone(),
360                Value::new_int(42),
361                Value::new_int(43)
362            ))
363            .to_string(),
364            "(42 . 43)"
365        );
366        assert_eq!(
367            print(Value::new_cons(
368                m.clone(),
369                Value::new_nil(),
370                Value::new_int(43)
371            ))
372            .to_string(),
373            "(() . 43)"
374        );
375    }
376
377    #[test]
378    fn print_lists() {
379        let m = Mutator::new_ref();
380
381        assert_eq!(
382            print(Value::new_list(m.clone(), [Value::new_int(42)])).to_string(),
383            "(42)"
384        );
385        assert_eq!(
386            print(Value::new_list(
387                m.clone(),
388                [Value::new_int(42), Value::new_int(43)]
389            ))
390            .to_string(),
391            "(42 43)"
392        );
393        assert_eq!(
394            print(Value::new_list(
395                m.clone(),
396                [Value::new_int(42), Value::new_int(43), Value::new_int(44)]
397            ))
398            .to_string(),
399            "(42 43 44)"
400        );
401    }
402
403    #[allow(clippy::similar_names)]
404    #[test]
405    fn print_cyclic_list() {
406        let m = Mutator::new_ref();
407
408        let list = Value::new_list(
409            m.clone(),
410            [Value::new_int(42), Value::new_int(43), Value::new_int(44)],
411        );
412        let mut last = list.as_pair().unwrap().cddr().expect("cddr");
413        last.as_pair_mut().unwrap().set_cdr(list.clone());
414
415        assert_eq!(
416            print(list.clone()).to_string(),
417            format!("(42 43 44 <cyclic list>)")
418        );
419        assert_eq!(
420            print(Value::new_cons(m.clone(), list.clone(), Value::new_nil())).to_string(),
421            format!("((42 43 44 <cyclic list>))")
422        );
423        assert_eq!(
424            print(Value::new_cons(m.clone(), Value::new_nil(), list.clone())).to_string(),
425            format!("(() 42 43 44 <cyclic list>)")
426        );
427        assert_eq!(
428            print(Value::new_cons(m.clone(), list.clone(), list)).to_string(),
429            format!("((42 43 44 <cyclic list>) 42 43 44 <cyclic list>)")
430        );
431    }
432
433    #[test]
434    fn print_cyclic_vector() {
435        let m = Mutator::new_ref();
436
437        let mut vector = Value::new_vector(
438            m.clone(),
439            &[Value::new_int(42), Value::new_int(43), Value::new_int(44)],
440        );
441        let cycle = vector.clone();
442
443        vector.as_vector_mut().unwrap().push(cycle);
444
445        assert_eq!(
446            print(vector).to_string(),
447            format!("#(42 43 44 <cyclic vector>)")
448        );
449    }
450}