1use crate::{Arena, Term, View};
7use std::fmt;
8
9pub struct TermDisplay<'a> {
29 term: &'a Term,
31 arena: &'a Arena,
33}
34
35impl Term {
36 #[inline]
44 pub fn display<'a>(&'a self, arena: &'a Arena) -> TermDisplay<'a> {
45 TermDisplay { term: self, arena }
46 }
47}
48
49impl<'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}