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 '{' => out.push_str("\\{"),
84 '}' => out.push_str("\\}"),
85 '\n' => out.push_str("\\n"),
86 '\r' => out.push_str("\\r"),
87 '\t' => out.push_str("\\t"),
88 '\x07' => out.push_str("\\a"),
89 '\x08' => out.push_str("\\b"),
90 '\x0C' => out.push_str("\\f"),
91 '\x0B' => out.push_str("\\v"),
92 '\x1B' => out.push_str("\\e"),
93 '\x7F' => out.push_str("\\d"),
94 c if c.is_control() => out.push_str(&format!("\\x{:02X}", c as u32)),
95 c => out.push(c),
96 }
97 }
98 out.push('"');
99 f.write_str(&out)
100 }
101
102 fn epoch_to_date_string(epoch_ms: i64, fmt: Option<&str>) -> String {
103 use chrono::{DateTime, Utc};
104
105 let secs = epoch_ms.div_euclid(1000);
106 let nsecs = (epoch_ms.rem_euclid(1000) * 1_000_000) as u32;
107
108 let dt_utc = DateTime::<Utc>::from_timestamp(secs, nsecs).unwrap();
109
110 String::from(match fmt {
111 None => dt_utc.to_rfc3339(),
112 Some(layout) => dt_utc.format(layout).to_string(),
113 })
114 }
115
116 fn write_args(f: &mut fmt::Formatter<'_>, arena: &Arena, args: &[Term]) -> fmt::Result {
117 for (i, t) in args.iter().enumerate() {
118 if i > 0 {
119 f.write_str(", ")?;
120 }
121 write!(f, "{}", t.display(arena))?;
122 }
123 Ok(())
124 }
125
126 match self.term.view(self.arena).map_err(|_e| fmt::Error)? {
127 View::Int(i) => write!(f, "{i}"),
128 View::Real(r) => {
129 if r.fract() == 0.0 {
130 write!(f, "{:.1}", r)
131 } else {
132 write!(f, "{}", r)
133 }
134 }
135 View::Date(epoch) => write!(f, "date{{{}}}", epoch_to_date_string(epoch, None)),
136 View::Str(s) => write_str_quoted(f, s),
137 View::Bin(bytes) => {
138 write!(f, "hex{{")?;
139 for b in bytes {
140 write!(f, "{:02X}", b)?;
141 }
142 write!(f, "}}")
143 }
144 View::Atom(a) => write_atom_str(f, a),
145 View::Var(v) => write!(f, "{}", v),
146
147 View::Func(ar, functor, args) => {
148 if args.is_empty() {
149 return write!(f, "/* invalid Func */");
150 }
151 write_atom(f, ar, functor)?;
152 write!(f, "(")?;
153 write_args(f, ar, args)?;
154 write!(f, ")")
155 }
156
157 View::Tuple(ar, items) => {
158 if items.is_empty() {
159 write!(f, "()")
160 } else {
161 write!(f, "(")?;
162 write_args(f, ar, items)?;
163 write!(f, ")")
164 }
165 }
166
167 View::List(ar, items, tail) => {
168 if items.is_empty() {
169 write!(f, "[]")
170 } else {
171 write!(f, "[")?;
172 write_args(f, ar, items)?;
173 if *tail != Term::NIL {
174 f.write_str(" | ")?;
175 write!(f, "{}", tail.display(ar))?;
176 }
177 write!(f, "]")
178 }
179 }
180 }
181 }
182}