1use crate::reader::SExpr;
7
8pub fn format_exprs(exprs: &[SExpr], indent: usize) -> String {
9 let mut out = String::new();
10 for (i, expr) in exprs.iter().enumerate() {
11 out.push_str(&format_expr(expr, indent));
12 if i + 1 < exprs.len() {
13 out.push('\n');
14 if indent == 0 {
16 out.push('\n');
17 }
18 }
19 }
20 out.push('\n');
21 out
22}
23
24fn format_expr(expr: &SExpr, indent: usize) -> String {
25 match expr {
26 SExpr::Atom(s) => s.clone(),
27 SExpr::Str(s) => format!("\"{}\"", escape_string(s)),
28 SExpr::Num(n) => {
29 if *n == (*n as i64) as f64 {
30 format!("{}", *n as i64)
31 } else {
32 format!("{}", n)
33 }
34 }
35 SExpr::List(values) => format_list(values, indent),
36 }
37}
38
39fn format_list(values: &[SExpr], indent: usize) -> String {
40 if values.is_empty() {
41 return "()".to_string();
42 }
43
44 let single = format_single_line(values);
46 if single.len() + indent <= 80 && !single.contains('\n') {
47 return format!("({})", single);
48 }
49
50 let head = format_expr(&values[0], 0);
52 let child_indent = indent + 2;
53 let child_prefix = " ".repeat(child_indent);
54
55 let mut out = format!("({}", head);
56 for val in &values[1..] {
57 let formatted = format_expr(val, child_indent);
58 out.push('\n');
59 out.push_str(&child_prefix);
60 out.push_str(&formatted);
61 }
62 out.push(')');
63 out
64}
65
66fn format_single_line(values: &[SExpr]) -> String {
67 values
68 .iter()
69 .map(|v| format_expr(v, 0))
70 .collect::<Vec<_>>()
71 .join(" ")
72}
73
74fn escape_string(s: &str) -> String {
75 s.replace('\\', "\\\\")
76 .replace('"', "\\\"")
77 .replace('\n', "\\n")
78 .replace('\t', "\\t")
79}
80
81#[cfg(test)]
82mod tests {
83 use super::*;
84 use crate::reader::SExpr;
85
86 #[test]
87 fn format_single_atom() {
88 let exprs = vec![SExpr::Atom("hello".into())];
89 assert_eq!(format_exprs(&exprs, 0), "hello\n");
90 }
91
92 #[test]
93 fn format_integer_number() {
94 let exprs = vec![SExpr::Num(42.0)];
95 assert_eq!(format_exprs(&exprs, 0), "42\n");
96 }
97
98 #[test]
99 fn format_float_number() {
100 let exprs = vec![SExpr::Num(3.14)];
101 assert_eq!(format_exprs(&exprs, 0), "3.14\n");
102 }
103
104 #[test]
105 fn format_string_simple() {
106 let exprs = vec![SExpr::Str("hello".into())];
107 assert_eq!(format_exprs(&exprs, 0), "\"hello\"\n");
108 }
109
110 #[test]
111 fn format_string_with_escapes() {
112 let exprs = vec![SExpr::Str("a\nb\t\"c\\".into())];
113 assert_eq!(format_exprs(&exprs, 0), "\"a\\nb\\t\\\"c\\\\\"\n");
114 }
115
116 #[test]
117 fn format_empty_list() {
118 let exprs = vec![SExpr::List(vec![])];
119 assert_eq!(format_exprs(&exprs, 0), "()\n");
120 }
121
122 #[test]
123 fn format_short_list() {
124 let exprs = vec![SExpr::List(vec![
125 SExpr::Atom("+".into()),
126 SExpr::Num(1.0),
127 SExpr::Num(2.0),
128 ])];
129 assert_eq!(format_exprs(&exprs, 0), "(+ 1 2)\n");
130 }
131
132 #[test]
133 fn format_long_list_wraps() {
134 let mut vals = vec![SExpr::Atom("function-with-a-very-long-name".into())];
136 for _ in 0..5 {
137 vals.push(SExpr::Str("some-really-long-argument-value".into()));
138 }
139 let exprs = vec![SExpr::List(vals)];
140 let result = format_exprs(&exprs, 0);
141 assert!(result.contains('\n'));
142 assert!(result.starts_with("(function-with-a-very-long-name"));
143 }
144
145 #[test]
146 fn format_multiple_top_level_exprs() {
147 let exprs = vec![SExpr::Atom("a".into()), SExpr::Atom("b".into())];
148 let result = format_exprs(&exprs, 0);
149 assert_eq!(result, "a\n\nb\n");
151 }
152
153 #[test]
154 fn format_nested_list() {
155 let inner = SExpr::List(vec![
156 SExpr::Atom("+".into()),
157 SExpr::Num(1.0),
158 SExpr::Num(2.0),
159 ]);
160 let outer = SExpr::List(vec![
161 SExpr::Atom("define".into()),
162 SExpr::Atom("x".into()),
163 inner,
164 ]);
165 let result = format_exprs(&vec![outer], 0);
166 assert_eq!(result, "(define x (+ 1 2))\n");
167 }
168
169 #[test]
170 fn format_indented_children() {
171 let exprs = vec![SExpr::List(vec![
172 SExpr::Atom("define".into()),
173 SExpr::Atom("x".into()),
174 ])];
175 let result = format_exprs(&exprs, 4);
177 assert_eq!(result, "(define x)\n");
178 }
179
180 #[test]
181 fn escape_string_empty() {
182 assert_eq!(escape_string(""), "");
183 }
184
185 #[test]
186 fn escape_string_no_special() {
187 assert_eq!(escape_string("hello"), "hello");
188 }
189
190 #[test]
191 fn escape_string_all_special() {
192 assert_eq!(escape_string("\\\"\n\t"), "\\\\\\\"\\n\\t");
193 }
194}