1use crate::ast::{BinaryOp, DoStatement, Expr, RecordEntry, RecordKey, SpannedExpr};
2use crate::ast_to_source::{expr_to_source};
3use crate::values::LambdaArg;
4
5const DEFAULT_MAX_COLUMNS: usize = 80;
6const INDENT_SIZE: usize = 2;
7
8pub fn format_expr(expr: &SpannedExpr, max_columns: Option<usize>) -> String {
10 let max_cols = max_columns.unwrap_or(DEFAULT_MAX_COLUMNS);
11 format_expr_impl(expr, max_cols, 0)
12}
13
14fn format_expr_impl(expr: &SpannedExpr, max_cols: usize, indent: usize) -> String {
16 if let Expr::Lambda { args, body } = &expr.node {
18 return format_lambda(args, body, max_cols, indent);
19 }
20
21 let single_line = format_single_line(expr);
23 let current_line_length = indent + single_line.len();
24
25 if current_line_length <= max_cols {
27 return single_line;
28 }
29
30 format_multiline(expr, max_cols, indent)
32}
33
34fn format_single_line(expr: &SpannedExpr) -> String {
36 match &expr.node {
37 Expr::Lambda { args, body } => {
38 let args_str: Vec<String> = args.iter().map(lambda_arg_to_str).collect();
39 let args_part = if args.len() == 1 && matches!(args[0], LambdaArg::Required(_)) {
40 args_str[0].clone()
41 } else {
42 format!("({})", args_str.join(", "))
43 };
44 format!("{} => {}", args_part, format_single_line(body))
45 }
46 Expr::Call { func, args } => {
47 let func_str = match &func.node {
48 Expr::Lambda { .. } => format!("({})", format_single_line(func)),
49 _ => format_single_line(func),
50 };
51 let args_str: Vec<String> = args.iter().map(format_single_line).collect();
52 format!("{}({})", func_str, args_str.join(", "))
53 }
54 Expr::List(items) => {
55 let items_str: Vec<String> = items.iter().map(format_single_line).collect();
56 format!("[{}]", items_str.join(", "))
57 }
58 Expr::Record(entries) => {
59 let entries_str: Vec<String> = entries.iter().map(|e| format_record_entry_single_line(e)).collect();
60 format!("{{{}}}", entries_str.join(", "))
61 }
62 _ => expr_to_source(expr),
64 }
65}
66
67fn format_record_entry_single_line(entry: &RecordEntry) -> String {
69 match &entry.key {
70 RecordKey::Static(key) => format!("{}: {}", key, format_single_line(&entry.value)),
71 RecordKey::Dynamic(key_expr) => {
72 format!("[{}]: {}", format_single_line(key_expr), format_single_line(&entry.value))
73 }
74 RecordKey::Shorthand(name) => name.clone(),
75 RecordKey::Spread(expr) => format_single_line(expr),
76 }
77}
78
79fn format_multiline(expr: &SpannedExpr, max_cols: usize, indent: usize) -> String {
81 match &expr.node {
82 Expr::List(items) => format_list_multiline(items, max_cols, indent),
83 Expr::Record(entries) => format_record_multiline(entries, max_cols, indent),
84 Expr::Conditional { condition, then_expr, else_expr } => {
85 format_conditional_multiline(condition, then_expr, else_expr, max_cols, indent)
86 }
87 Expr::Call { func, args } => format_call_multiline(func, args, max_cols, indent),
88 Expr::BinaryOp { op, left, right } => {
89 format_binary_op_multiline(op, left, right, max_cols, indent)
90 }
91 Expr::DoBlock { statements, return_expr } => {
92 format_do_block_multiline(statements, return_expr, indent)
93 }
94 _ => expr_to_source(expr),
96 }
97}
98
99fn format_list_multiline(items: &[SpannedExpr], max_cols: usize, indent: usize) -> String {
101 if items.is_empty() {
102 return "[]".to_string();
103 }
104
105 let inner_indent = indent + INDENT_SIZE;
106 let indent_str = make_indent(inner_indent);
107
108 let mut result = "[".to_string();
109
110 for item in items.iter() {
111 result.push_str("\n");
112 result.push_str(&indent_str);
113 result.push_str(&format_expr_impl(item, max_cols, inner_indent));
114
115 result.push(',');
117 }
118
119 result.push_str("\n");
120 result.push_str(&make_indent(indent));
121 result.push(']');
122
123 result
124}
125
126fn format_record_multiline(entries: &[RecordEntry], max_cols: usize, indent: usize) -> String {
128 if entries.is_empty() {
129 return "{}".to_string();
130 }
131
132 let inner_indent = indent + INDENT_SIZE;
133 let indent_str = make_indent(inner_indent);
134
135 let mut result = "{".to_string();
136
137 for entry in entries {
138 result.push_str("\n");
139 result.push_str(&indent_str);
140 result.push_str(&format_record_entry(entry, max_cols, inner_indent));
141
142 result.push(',');
144 }
145
146 result.push_str("\n");
147 result.push_str(&make_indent(indent));
148 result.push('}');
149
150 result
151}
152
153fn format_record_entry(entry: &RecordEntry, max_cols: usize, indent: usize) -> String {
155 match &entry.key {
156 RecordKey::Static(key) => {
157 format!("{}: {}", key, format_expr_impl(&entry.value, max_cols, indent))
158 }
159 RecordKey::Dynamic(key_expr) => {
160 format!(
161 "[{}]: {}",
162 format_expr_impl(key_expr, max_cols, indent),
163 format_expr_impl(&entry.value, max_cols, indent)
164 )
165 }
166 RecordKey::Shorthand(name) => name.clone(),
167 RecordKey::Spread(expr) => format_expr_impl(expr, max_cols, indent),
168 }
169}
170
171fn format_lambda(args: &[LambdaArg], body: &SpannedExpr, max_cols: usize, indent: usize) -> String {
173 let args_str: Vec<String> = args.iter().map(lambda_arg_to_str).collect();
174
175 let args_part = if args.len() == 1 && matches!(args[0], LambdaArg::Required(_)) {
177 format!("{} =>", args_str[0])
178 } else {
179 format!("({}) =>", args_str.join(", "))
180 };
181
182 let single_line_body = format_expr_impl(body, max_cols, indent);
184 let single_line = format!("{} {}", args_part, single_line_body);
185
186 if indent + single_line.len() <= max_cols {
187 return single_line;
188 }
189
190 let body_indent = indent + INDENT_SIZE;
192 format!(
193 "{}\n{}{}",
194 args_part,
195 make_indent(body_indent),
196 format_expr_impl(body, max_cols, body_indent)
197 )
198}
199
200fn format_conditional_multiline(
202 condition: &SpannedExpr,
203 then_expr: &SpannedExpr,
204 else_expr: &SpannedExpr,
205 max_cols: usize,
206 indent: usize,
207) -> String {
208 let cond_str = format_expr_impl(condition, max_cols, indent);
209
210 let if_then_prefix = format!("if {} then", cond_str);
212
213 if indent + if_then_prefix.len() <= max_cols {
214 let inner_indent = indent + INDENT_SIZE;
216 format!(
217 "{}\n{}{}\nelse\n{}{}",
218 if_then_prefix,
219 make_indent(inner_indent),
220 format_expr_impl(then_expr, max_cols, inner_indent),
221 make_indent(inner_indent),
222 format_expr_impl(else_expr, max_cols, inner_indent)
223 )
224 } else {
225 let inner_indent = indent + INDENT_SIZE;
227 format!(
228 "if\n{}{}\nthen\n{}{}\nelse\n{}{}",
229 make_indent(inner_indent),
230 format_expr_impl(condition, max_cols, inner_indent),
231 make_indent(inner_indent),
232 format_expr_impl(then_expr, max_cols, inner_indent),
233 make_indent(inner_indent),
234 format_expr_impl(else_expr, max_cols, inner_indent)
235 )
236 }
237}
238
239fn format_call_multiline(func: &SpannedExpr, args: &[SpannedExpr], max_cols: usize, indent: usize) -> String {
241 let func_str = match &func.node {
242 Expr::Lambda { .. } => format!("({})", format_expr_impl(func, max_cols, indent)),
243 _ => format_expr_impl(func, max_cols, indent),
244 };
245
246 if args.is_empty() {
247 return format!("{}()", func_str);
248 }
249
250 let inner_indent = indent + INDENT_SIZE;
252 let indent_str = make_indent(inner_indent);
253
254 let mut result = format!("{}(", func_str);
255
256 for (i, arg) in args.iter().enumerate() {
257 result.push_str("\n");
258 result.push_str(&indent_str);
259 result.push_str(&format_expr_impl(arg, max_cols, inner_indent));
260
261 if i < args.len() - 1 {
262 result.push(',');
263 } else {
264 result.push(',');
266 }
267 }
268
269 result.push_str("\n");
270 result.push_str(&make_indent(indent));
271 result.push(')');
272
273 result
274}
275
276fn format_binary_op_multiline(
278 op: &BinaryOp,
279 left: &SpannedExpr,
280 right: &SpannedExpr,
281 max_cols: usize,
282 indent: usize,
283) -> String {
284 let op_str = binary_op_str(op);
285 let left_str = format_expr_impl(left, max_cols, indent);
286
287 let right_indent = indent + INDENT_SIZE;
289 format!(
290 "{}\n{}{} {}",
291 left_str,
292 make_indent(right_indent),
293 op_str,
294 format_expr_impl(right, max_cols, right_indent)
295 )
296}
297
298fn format_do_block_multiline(statements: &[DoStatement], return_expr: &SpannedExpr, indent: usize) -> String {
300 let inner_indent = indent + INDENT_SIZE;
301 let indent_str = make_indent(inner_indent);
302
303 let mut result = "do {".to_string();
304
305 for stmt in statements {
306 match stmt {
307 DoStatement::Expression(e) => {
308 result.push_str("\n");
309 result.push_str(&indent_str);
310 result.push_str(&format_expr_impl(e, usize::MAX, inner_indent));
312 }
313 DoStatement::Comment(c) => {
314 result.push_str("\n");
315 result.push_str(&indent_str);
316 result.push_str(c);
317 }
318 }
319 }
320
321 result.push_str("\n");
322 result.push_str(&indent_str);
323 result.push_str("return ");
324 result.push_str(&format_expr_impl(return_expr, usize::MAX, inner_indent));
325 result.push_str("\n");
326 result.push_str(&make_indent(indent));
327 result.push('}');
328
329 result
330}
331
332fn lambda_arg_to_str(arg: &LambdaArg) -> String {
334 match arg {
335 LambdaArg::Required(name) => name.clone(),
336 LambdaArg::Optional(name) => format!("{}?", name),
337 LambdaArg::Rest(name) => format!("...{}", name),
338 }
339}
340
341fn binary_op_str(op: &BinaryOp) -> &'static str {
343 match op {
344 BinaryOp::Add => "+",
345 BinaryOp::Subtract => "-",
346 BinaryOp::Multiply => "*",
347 BinaryOp::Divide => "/",
348 BinaryOp::Modulo => "%",
349 BinaryOp::Power => "^",
350 BinaryOp::Equal => "==",
351 BinaryOp::NotEqual => "!=",
352 BinaryOp::Less => "<",
353 BinaryOp::LessEq => "<=",
354 BinaryOp::Greater => ">",
355 BinaryOp::GreaterEq => ">=",
356 BinaryOp::DotEqual => ".==",
357 BinaryOp::DotNotEqual => ".!=",
358 BinaryOp::DotLess => ".<",
359 BinaryOp::DotLessEq => ".<=",
360 BinaryOp::DotGreater => ".>",
361 BinaryOp::DotGreaterEq => ".>=",
362 BinaryOp::And => "&&",
363 BinaryOp::NaturalAnd => "and",
364 BinaryOp::Or => "||",
365 BinaryOp::NaturalOr => "or",
366 BinaryOp::Via => "via",
367 BinaryOp::Into => "into",
368 BinaryOp::Where => "where",
369 BinaryOp::Coalesce => "??",
370 }
371}
372
373fn make_indent(indent: usize) -> String {
375 " ".repeat(indent)
376}
377
378#[cfg(test)]
379mod tests {
380 use super::*;
381 use crate::parser::get_pairs;
382 use crate::expressions::pairs_to_expr;
383
384 fn parse_test_expr(source: &str) -> SpannedExpr {
385 use crate::parser::Rule;
386
387 let pairs = get_pairs(source).unwrap();
388
389 for pair in pairs {
391 if pair.as_rule() == Rule::statement {
392 if let Some(inner_pair) = pair.into_inner().next() {
393 return pairs_to_expr(inner_pair.into_inner()).unwrap();
394 }
395 }
396 }
397
398 panic!("No statement found in parsed input");
399 }
400
401 #[test]
402 fn test_format_short_list() {
403 let expr = parse_test_expr("[1, 2, 3]");
404 let formatted = format_expr(&expr, Some(80));
405 assert_eq!(formatted, "[1, 2, 3]");
406 }
407
408 #[test]
409 fn test_format_long_list() {
410 let expr = parse_test_expr("[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]");
411 let formatted = format_expr(&expr, Some(40));
412 assert!(formatted.contains("\n"));
413 assert!(formatted.contains("[\n"));
414 }
415
416 #[test]
417 fn test_format_short_record() {
418 let expr = parse_test_expr("{x: 1, y: 2}");
419 let formatted = format_expr(&expr, Some(80));
420 assert_eq!(formatted, "{x: 1, y: 2}");
421 }
422
423 #[test]
424 fn test_format_long_record() {
425 let expr = parse_test_expr("{name: \"Alice\", age: 30, email: \"alice@example.com\", address: \"123 Main St\"}");
426 let formatted = format_expr(&expr, Some(40));
427 assert!(formatted.contains("\n"));
428 assert!(formatted.contains("{\n"));
429 }
430
431 #[test]
432 fn test_format_conditional() {
433 let expr = parse_test_expr("if very_long_condition_variable > 100 then \"yes\" else \"no\"");
434 let formatted = format_expr(&expr, Some(30));
435 assert!(formatted.contains("\n"));
436 }
437
438 #[test]
439 fn test_format_binary_op() {
440 let expr = parse_test_expr("very_long_variable_name + another_very_long_variable_name");
441 let formatted = format_expr(&expr, Some(30));
442 assert!(formatted.contains("\n"));
443 }
444
445 #[test]
446 fn test_format_lambda() {
447 let expr = parse_test_expr("(x, y) => x + y");
448 let formatted = format_expr(&expr, Some(80));
449 assert_eq!(formatted, "(x, y) => x + y");
450 }
451
452 #[test]
453 fn test_format_nested_list() {
454 let expr = parse_test_expr("[[1, 2, 3], [4, 5, 6], [7, 8, 9]]");
455 let formatted = format_expr(&expr, Some(20));
456 assert!(formatted.contains("\n"));
457 }
458
459 #[test]
460 fn test_format_function_call() {
461 let expr = parse_test_expr("map([1, 2, 3], x => x * 2)");
462 let formatted = format_expr(&expr, Some(80));
463 assert_eq!(formatted, "map([1, 2, 3], x => x * 2)");
464 }
465
466 #[test]
467 fn test_format_do_block() {
468 let expr = parse_test_expr("do { x = 1\n return x }");
469 let formatted = format_expr(&expr, Some(80));
470 assert!(formatted.contains("do {"));
471 assert!(formatted.contains("return"));
472 }
473}