1#[derive(Debug, Clone, PartialEq)]
2pub enum Form {
3 Nil,
4 Bool(bool),
5 Number(i64),
6 Float(f64),
7 BigInteger(BigInt),
8 Character(char),
9 Regex(String),
10 Tagged(String, Box<Form>),
11 Metadata(Box<Form>, Box<Form>),
12 Symbol(String),
13 Keyword(String),
14 String(String),
15 Map(Vec<(Form, Form)>),
16 Set(Vec<Form>),
17 Vector(Vec<Form>),
18 List(Vec<Form>),
19}
20
21pub(crate) fn display_string(value: &str) -> String {
22 let mut output = String::from("\"");
23 for ch in value.chars() {
24 match ch {
25 '\n' => output.push_str("\\n"),
26 '\r' => output.push_str("\\r"),
27 '\t' => output.push_str("\\t"),
28 '\u{0008}' => output.push_str("\\b"),
29 '\u{000c}' => output.push_str("\\f"),
30 '\\' => output.push_str("\\\\"),
31 '"' => output.push_str("\\\""),
32 ch if ch.is_control() => output.push_str(&format!("\\u{:04X}", ch as u32)),
33 ch => output.push(ch),
34 }
35 }
36 output.push('"');
37 output
38}
39
40pub(crate) fn display_regex(value: &str) -> String {
41 let mut output = String::from("#\"");
42 let mut backslashes = 0usize;
43 for ch in value.chars() {
44 if ch == '"' && backslashes % 2 == 0 {
45 output.push('\\');
46 }
47 output.push(ch);
48 backslashes = if ch == '\\' { backslashes + 1 } else { 0 };
49 }
50 output.push('"');
51 output
52}
53
54fn display_forms(values: &[Form], start: &str, end: &str) -> String {
55 format!(
56 "{start}{}{end}",
57 values
58 .iter()
59 .map(ToString::to_string)
60 .collect::<Vec<_>>()
61 .join(" ")
62 )
63}
64
65impl std::fmt::Display for Form {
66 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67 let output = match self {
68 Self::Nil => "nil".into(),
69 Self::Bool(value) => value.to_string(),
70 Self::Number(value) => value.to_string(),
71 Self::Float(value) if value.is_finite() && value.fract() == 0.0 => {
72 format!("(double {value:.1})")
73 }
74 Self::Float(value) if value.is_finite() => format!("(double {value})"),
75 Self::Float(_) => panic!("non-finite number"),
76 Self::BigInteger(value) => value.to_string(),
77 Self::Character('\n') => "\\newline".into(),
78 Self::Character(' ') => "\\space".into(),
79 Self::Character('\t') => "\\tab".into(),
80 Self::Character('\u{0008}') => "\\backspace".into(),
81 Self::Character('\u{000c}') => "\\formfeed".into(),
82 Self::Character('\r') => "\\return".into(),
83 Self::Character(value) if value.is_control() => format!("\\u{:04X}", *value as u32),
84 Self::Character(value) => format!("\\{value}"),
85 Self::Regex(value) => display_regex(value),
86 Self::Tagged(tag, value) => format!("#{tag}{value}"),
87 Self::Metadata(_, value) => value.to_string(),
88 Self::Symbol(value) => value.clone(),
89 Self::Keyword(value) => format!(":{value}"),
90 Self::String(value) => display_string(value),
91 Self::Map(entries) => {
92 let values = entries
93 .iter()
94 .flat_map(|(key, value)| [key.to_string(), value.to_string()])
95 .collect::<Vec<_>>();
96 format!("{{{}}}", values.join(" "))
97 }
98 Self::Set(values) => display_forms(values, "#{", "}"),
99 Self::Vector(values) => display_forms(values, "[", "]"),
100 Self::List(values) => display_forms(values, "(", ")"),
101 };
102 formatter.write_str(&output)
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use super::Form;
109 use crate::kernel::parse;
110
111 #[test]
112 fn canonical_readable_forms_round_trip() {
113 let sources = [
114 "nil",
115 "true",
116 "-42",
117 "\\newline",
118 "\\u0000",
119 "\"line\\nvalue\"",
120 ":hara/name",
121 "hara/name",
122 "(quote [1 2 3])",
123 "{:message \"value\" :flags #{:a :b}}",
124 "#math[:tensor 42]",
125 "#\"\\d+\"",
126 ];
127 for regex in [Form::Regex(r#"\""#.into()), Form::Regex(r#"\\\""#.into())] {
128 let readable = regex.to_string();
129 assert_eq!(parse(&readable).unwrap(), regex, "{readable}");
130 }
131
132 for source in sources {
133 let form = parse(source).unwrap();
134 let readable = form.to_string();
135 assert_eq!(parse(&readable).unwrap(), form, "{source} -> {readable}");
136 }
137 }
138
139 #[test]
140 fn metadata_printing_is_canonical() {
141 let metadata = parse("^:private [1]").unwrap();
142 assert_eq!(metadata.to_string(), "[1]");
143 assert_eq!(parse(&metadata.to_string()).unwrap(), parse("[1]").unwrap());
144 }
145}
146use num_bigint::BigInt;