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#[must_use]
26#[inline]
27pub const fn print(value: Value<'_>) -> SimplePrinter<'_> {
28 SimplePrinter(value)
29}
30
31#[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
55pub 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}