formualizer_parse/
pretty.rs1use crate::parser::{ASTNode, ASTNodeType, Parser, ParserError};
2use crate::tokenizer::Tokenizer;
3
4pub fn pretty_print(ast: &ASTNode) -> String {
14 match &ast.node_type {
15 ASTNodeType::Literal(value) => match value {
16 crate::LiteralValue::Text(s) => {
18 let escaped = s.replace('"', "\"\"");
19 format!("\"{escaped}\"")
20 }
21 _ => format!("{value}"),
22 },
23 ASTNodeType::Reference { reference, .. } => reference.normalise(),
24 ASTNodeType::UnaryOp { op, expr } => {
25 format!("{}{}", op, pretty_print(expr))
26 }
27 ASTNodeType::BinaryOp { op, left, right } => {
28 if op == ":" {
30 format!("{}:{}", pretty_print(left), pretty_print(right))
31 } else {
32 format!("{} {} {}", pretty_print(left), op, pretty_print(right))
33 }
34 }
35 ASTNodeType::Function { name, args } => {
36 let args_str = args
37 .iter()
38 .map(pretty_print)
39 .collect::<Vec<String>>()
40 .join(", ");
41
42 format!("{}({})", name.to_uppercase(), args_str)
43 }
44 ASTNodeType::Array(rows) => {
45 let rows_str = rows
46 .iter()
47 .map(|row| {
48 row.iter()
49 .map(pretty_print)
50 .collect::<Vec<String>>()
51 .join(", ")
52 })
53 .collect::<Vec<String>>()
54 .join("; ");
55
56 format!("{{{rows_str}}}")
57 }
58 }
59}
60
61pub fn canonical_formula(ast: &ASTNode) -> String {
66 format!("={}", pretty_print(ast))
67}
68
69pub fn pretty_parse_render(formula: &str) -> Result<String, ParserError> {
73 if formula.is_empty() {
75 return Ok(String::new());
76 }
77
78 let needs_equals = !formula.starts_with('=');
80 let formula_to_parse = if needs_equals {
81 format!("={formula}")
82 } else {
83 formula.to_string()
84 };
85
86 let tokenizer = match Tokenizer::new(&formula_to_parse) {
88 Ok(t) => t,
89 Err(e) => {
90 return Err(ParserError {
91 message: format!("Tokenizer error: {}", e.message),
92 position: None,
93 });
94 }
95 };
96
97 let mut parser = Parser::new(tokenizer.items, false);
98 let ast = parser.parse()?;
99
100 let pretty_printed = pretty_print(&ast);
102
103 if needs_equals {
105 Ok(pretty_printed)
106 } else {
107 Ok(format!("={pretty_printed}"))
108 }
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114
115 #[test]
116 fn test_pretty_print_validation() {
117 let original = "= sum( a1 ,2 ) ";
118 let pretty = pretty_parse_render(original).unwrap();
119 assert_eq!(pretty, "=SUM(A1, 2)");
120
121 let round = pretty_parse_render(&pretty).unwrap();
122 assert_eq!(pretty, round); }
124
125 #[test]
126 fn test_ast_canonicalization() {
127 let formula = "=sum( a1, b2 )";
129 let pretty = pretty_parse_render(formula).unwrap();
130
131 assert_eq!(pretty, "=SUM(A1, B2)");
133
134 let repretty = pretty_parse_render(&pretty).unwrap();
136 assert_eq!(pretty, repretty);
137 }
138
139 #[test]
140 fn test_pretty_print_operators() {
141 let formula = "=a1+b2*3";
142 let pretty = pretty_parse_render(formula).unwrap();
143 assert_eq!(pretty, "=A1 + B2 * 3");
144
145 let formula = "=a1 + b2 * 3";
146 let pretty = pretty_parse_render(formula).unwrap();
147 assert_eq!(pretty, "=A1 + B2 * 3");
148 }
149
150 #[test]
151 fn test_pretty_print_function_nesting() {
152 let formula = "=if(a1>0, sum(b1:b10), average(c1:c10))";
153 let pretty = pretty_parse_render(formula).unwrap();
154 assert_eq!(pretty, "=IF(A1 > 0, SUM(B1:B10), AVERAGE(C1:C10))");
155 }
156
157 #[test]
158 fn test_pretty_print_arrays() {
159 let formula = "={1,2;3,4}";
160 let pretty = pretty_parse_render(formula).unwrap();
161 assert_eq!(pretty, "={1, 2; 3, 4}");
162
163 let formula = "={1, 2; 3, 4}";
164 let pretty = pretty_parse_render(formula).unwrap();
165 assert_eq!(pretty, "={1, 2; 3, 4}");
166 }
167
168 #[test]
169 fn test_pretty_print_references() {
170 let formula = "=Sheet1!$a$1:$b$2";
171 let pretty = pretty_parse_render(formula).unwrap();
172 assert_eq!(pretty, "=Sheet1!A1:B2");
173
174 let formula = "='My Sheet'!a1";
175 let pretty = pretty_parse_render(formula).unwrap();
176 assert_eq!(pretty, "='My Sheet'!A1");
177 }
178
179 #[test]
180 fn test_pretty_print_text_literals_in_functions() {
181 let formula = "=SUMIFS(A:A, B:B, \"*Parking*\")";
183 let pretty = pretty_parse_render(formula).unwrap();
184 assert_eq!(pretty, "=SUMIFS(A:A, B:B, \"*Parking*\")");
185 }
186
187 #[test]
188 fn test_pretty_print_text_concatenation_and_escaping() {
189 let formula = "=\">=\"&DATE(2024,1,1)";
191 let pretty = pretty_parse_render(formula).unwrap();
192 assert_eq!(pretty, "=\">=\" & DATE(2024, 1, 1)");
193
194 let formula = "=\"He said \"\"Hi\"\"\"";
196 let pretty = pretty_parse_render(formula).unwrap();
197 assert_eq!(pretty, "=\"He said \"\"Hi\"\"\"");
198 }
199
200 #[test]
201 fn test_pretty_print_text_in_arrays() {
202 let formula = "={\"A\", \"B\"; \"C\", \"D\"}";
203 let pretty = pretty_parse_render(formula).unwrap();
204 assert_eq!(pretty, "={\"A\", \"B\"; \"C\", \"D\"}");
205 }
206}