arena_terms/
display.rs

1//! Defines [`TermDisplay`], a formatter for rendering [`Term`] values.
2//!
3//! Provides a [`fmt::Display`] implementation for human-readable output of
4//! terms, including atoms, lists, tuples, and compound structures.
5
6use crate::{Arena, Term, View};
7use std::fmt;
8
9/// A wrapper that ties together a [`Term`] and its [`Arena`], forming the
10/// basis for configurable pretty-printing. This type is designed as the
11/// foundation on which flexible formatting and printing of terms will be built.
12///
13/// It already implements [`fmt::Display`], so you can seamlessly use it with
14/// standard formatting macros (`format!`, `println!`, etc.) to render
15/// terms. In the future, it will also support additional, customizable
16/// formatting options for advanced pretty-printing.
17///
18/// ### Example
19/// ```rust
20/// use arena_terms::{Term, Arena, func, IntoTerm};
21/// let mut arena = Arena::new();
22/// let term = func!("foo"; 1, "hello, world!" => &mut arena);
23///
24/// println!("{}", term.display(&arena));
25/// ```
26///
27/// Construct instances via [`Term::display`] or [`Arena::display`].
28pub struct TermDisplay<'a> {
29    /// The interned term to display.
30    term: &'a Term,
31    /// The arena where the term is stored.
32    arena: &'a Arena,
33}
34
35impl Term {
36    /// Return a [`TermDisplay`] suitable for formatting with [`fmt::Display`].
37    ///
38    /// Use this method when you want to render a term:
39    ///
40    /// ```ignore
41    /// println!("{}", term.display(&arena));
42    /// ```
43    #[inline]
44    pub fn display<'a>(&'a self, arena: &'a Arena) -> TermDisplay<'a> {
45        TermDisplay { term: self, arena }
46    }
47}
48
49/// Implements [`fmt::Display`] for [`TermDisplay`], enabling it to be
50/// formatted and printed with standard formatting macros.
51impl<'a> fmt::Display for TermDisplay<'a> {
52    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53        fn is_unquoted_atom(s: &str) -> bool {
54            let mut chars = s.chars();
55            match chars.next() {
56                Some(c) if c.is_ascii_lowercase() => {}
57                _ => return false,
58            }
59            chars.all(|c| c.is_ascii_alphanumeric() || c == '_')
60        }
61
62        fn write_atom(f: &mut fmt::Formatter<'_>, arena: &Arena, functor: &Term) -> fmt::Result {
63            let s = arena.atom_name(functor).map_err(|_e| fmt::Error)?;
64            write_atom_str(f, s)
65        }
66
67        fn write_atom_str(f: &mut fmt::Formatter<'_>, s: &str) -> fmt::Result {
68            if s.is_empty() || !is_unquoted_atom(s) {
69                let escaped = s.replace('\'', "\\'");
70                write!(f, "'{}'", escaped)
71            } else {
72                write!(f, "{}", s)
73            }
74        }
75
76        fn write_str_quoted(f: &mut fmt::Formatter<'_>, s: &str) -> fmt::Result {
77            let mut out = String::new();
78            out.push('"');
79            for ch in s.chars() {
80                match ch {
81                    '\\' => out.push_str("\\\\"),
82                    '"' => out.push_str("\\\""),
83                    '\n' => out.push_str("\\n"),
84                    '\r' => out.push_str("\\r"),
85                    '\t' => out.push_str("\\t"),
86                    c if c.is_control() => out.push_str(&format!("\\x{:02X}\\", c as u32)),
87                    c => out.push(c),
88                }
89            }
90            out.push('"');
91            f.write_str(&out)
92        }
93
94        fn epoch_to_date_string(epoch_ms: i64, fmt: Option<&str>) -> String {
95            use chrono::{DateTime, Utc};
96
97            let secs = epoch_ms.div_euclid(1000);
98            let nsecs = (epoch_ms.rem_euclid(1000) * 1_000_000) as u32;
99
100            let dt_utc = DateTime::<Utc>::from_timestamp(secs, nsecs).unwrap();
101
102            String::from(match fmt {
103                None => dt_utc.to_rfc3339(),
104                Some(layout) => dt_utc.format(layout).to_string(),
105            })
106        }
107
108        fn write_args(f: &mut fmt::Formatter<'_>, arena: &Arena, args: &[Term]) -> fmt::Result {
109            for (i, t) in args.iter().enumerate() {
110                if i > 0 {
111                    f.write_str(", ")?;
112                }
113                write!(f, "{}", t.display(arena))?;
114            }
115            Ok(())
116        }
117
118        match self.term.view(self.arena).map_err(|_e| fmt::Error)? {
119            View::Int(i) => write!(f, "{i}"),
120            View::Real(r) => {
121                if r.fract() == 0.0 {
122                    write!(f, "{:.1}", r)
123                } else {
124                    write!(f, "{}", r)
125                }
126            }
127            View::Date(epoch) => write!(f, "date{{{}}}", epoch_to_date_string(epoch, None)),
128            View::Str(s) => write_str_quoted(f, s),
129            View::Bin(bytes) => {
130                write!(f, "hex{{")?;
131                for b in bytes {
132                    write!(f, "{:02X}", b)?;
133                }
134                write!(f, "}}")
135            }
136            View::Atom(a) => write_atom_str(f, a),
137            View::Var(v) => write!(f, "{}", v),
138
139            View::Func(ar, functor, args) => {
140                if args.is_empty() {
141                    return write!(f, "/* invalid Func */");
142                }
143                write_atom(f, ar, functor)?;
144                write!(f, "(")?;
145                write_args(f, ar, args)?;
146                write!(f, ")")
147            }
148
149            View::Tuple(ar, items) => {
150                if items.is_empty() {
151                    write!(f, "()")
152                } else {
153                    write!(f, "(")?;
154                    write_args(f, ar, items)?;
155                    write!(f, ")")
156                }
157            }
158
159            View::List(ar, items, tail) => {
160                if items.is_empty() {
161                    write!(f, "[]")
162                } else {
163                    write!(f, "[")?;
164                    write_args(f, ar, items)?;
165                    if *tail != Term::NIL {
166                        f.write_str(" | ")?;
167                        write!(f, "{}", tail.display(ar))?;
168                    }
169                    write!(f, "]")
170                }
171            }
172        }
173    }
174}