1use crate::ast::{BinaryOp, DoStatement, Expr, RecordEntry, RecordKey, SpannedExpr};
2use crate::ast_to_source::{expr_to_source, format_record_key, needs_parens_in_binop};
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 if let Expr::DoBlock { .. } = &expr.node {
24 return format_multiline(expr, max_cols, indent);
25 }
26
27 let single_line = format_single_line(expr);
29
30 let first_line = single_line.lines().next().unwrap_or(&single_line);
33 let current_line_length = indent + first_line.len();
34
35 if !single_line.contains('\n') && current_line_length <= max_cols {
37 return single_line;
38 }
39
40 format_multiline(expr, max_cols, indent)
42}
43
44fn format_single_line(expr: &SpannedExpr) -> String {
46 match &expr.node {
47 Expr::Output { expr: inner_expr } => {
48 format!("output {}", format_single_line(inner_expr))
49 }
50 Expr::Lambda { args, body } => {
51 let args_str: Vec<String> = args.iter().map(lambda_arg_to_str).collect();
52 let args_part = if args.len() == 1 && matches!(args[0], LambdaArg::Required(_)) {
53 args_str[0].clone()
54 } else {
55 format!("({})", args_str.join(", "))
56 };
57 format!("{} => {}", args_part, format_single_line(body))
58 }
59 Expr::Call { func, args } => {
60 let func_str = match &func.node {
61 Expr::Lambda { .. } => format!("({})", format_single_line(func)),
62 _ => format_single_line(func),
63 };
64 let args_str: Vec<String> = args.iter().map(format_single_line).collect();
65 format!("{}({})", func_str, args_str.join(", "))
66 }
67 Expr::List(items) => {
68 let items_str: Vec<String> = items.iter().map(format_single_line).collect();
69 format!("[{}]", items_str.join(", "))
70 }
71 Expr::Record(entries) => {
72 let entries_str: Vec<String> = entries
73 .iter()
74 .map(format_record_entry_single_line)
75 .collect();
76 format!("{{{}}}", entries_str.join(", "))
77 }
78 _ => expr_to_source(expr),
80 }
81}
82
83fn format_record_entry_single_line(entry: &RecordEntry) -> String {
85 match &entry.key {
86 RecordKey::Static(key) => format!(
87 "{}: {}",
88 format_record_key(key),
89 format_single_line(&entry.value)
90 ),
91 RecordKey::Dynamic(key_expr) => {
92 format!(
93 "[{}]: {}",
94 format_single_line(key_expr),
95 format_single_line(&entry.value)
96 )
97 }
98 RecordKey::Shorthand(name) => name.clone(),
99 RecordKey::Spread(expr) => format_single_line(expr),
100 }
101}
102
103fn format_multiline(expr: &SpannedExpr, max_cols: usize, indent: usize) -> String {
105 match &expr.node {
106 Expr::Output { expr: inner_expr } => {
107 let formatted_inner = format_expr_impl(inner_expr, max_cols, indent);
109 format!("output {}", formatted_inner)
110 }
111 Expr::Assignment { ident, value } => {
112 format_assignment_multiline(ident, value, max_cols, indent)
113 }
114 Expr::List(items) => format_list_multiline(items, max_cols, indent),
115 Expr::Record(entries) => format_record_multiline(entries, max_cols, indent),
116 Expr::Conditional {
117 condition,
118 then_expr,
119 else_expr,
120 } => format_conditional_multiline(condition, then_expr, else_expr, max_cols, indent),
121 Expr::Call { func, args } => format_call_multiline(func, args, max_cols, indent),
122 Expr::BinaryOp { op, left, right } => {
123 format_binary_op_multiline(op, left, right, max_cols, indent)
124 }
125 Expr::DoBlock {
126 statements,
127 return_expr,
128 } => format_do_block_multiline(statements, return_expr, max_cols, indent),
129 _ => expr_to_source(expr),
131 }
132}
133
134fn format_assignment_multiline(
136 ident: &str,
137 value: &SpannedExpr,
138 max_cols: usize,
139 indent: usize,
140) -> String {
141 let prefix = format!("{} = ", ident);
146
147 let formatted_value = format_expr_impl(value, max_cols, indent);
150
151 format!("{}{}", prefix, formatted_value)
152}
153
154fn format_list_multiline(items: &[SpannedExpr], max_cols: usize, indent: usize) -> String {
156 if items.is_empty() {
157 return "[]".to_string();
158 }
159
160 let inner_indent = indent + INDENT_SIZE;
161 let indent_str = make_indent(inner_indent);
162
163 let mut result = "[".to_string();
164
165 for item in items.iter() {
166 result.push('\n');
167 result.push_str(&indent_str);
168 result.push_str(&format_expr_impl(item, max_cols, inner_indent));
169
170 result.push(',');
172 }
173
174 result.push('\n');
175 result.push_str(&make_indent(indent));
176 result.push(']');
177
178 result
179}
180
181fn format_record_multiline(entries: &[RecordEntry], max_cols: usize, indent: usize) -> String {
183 if entries.is_empty() {
184 return "{}".to_string();
185 }
186
187 let inner_indent = indent + INDENT_SIZE;
188 let indent_str = make_indent(inner_indent);
189
190 let mut result = "{".to_string();
191
192 for entry in entries {
193 result.push('\n');
194 result.push_str(&indent_str);
195 result.push_str(&format_record_entry(entry, max_cols, inner_indent));
196
197 result.push(',');
199 }
200
201 result.push('\n');
202 result.push_str(&make_indent(indent));
203 result.push('}');
204
205 result
206}
207
208fn format_record_entry(entry: &RecordEntry, max_cols: usize, indent: usize) -> String {
210 match &entry.key {
211 RecordKey::Static(key) => {
212 format!(
213 "{}: {}",
214 format_record_key(key),
215 format_expr_impl(&entry.value, max_cols, indent)
216 )
217 }
218 RecordKey::Dynamic(key_expr) => {
219 format!(
220 "[{}]: {}",
221 format_expr_impl(key_expr, max_cols, indent),
222 format_expr_impl(&entry.value, max_cols, indent)
223 )
224 }
225 RecordKey::Shorthand(name) => name.clone(),
226 RecordKey::Spread(expr) => format_expr_impl(expr, max_cols, indent),
227 }
228}
229
230fn format_lambda(args: &[LambdaArg], body: &SpannedExpr, max_cols: usize, indent: usize) -> String {
232 let args_str: Vec<String> = args.iter().map(lambda_arg_to_str).collect();
233
234 let args_part = if args.len() == 1 && matches!(args[0], LambdaArg::Required(_)) {
236 format!("{} =>", args_str[0])
237 } else {
238 format!("({}) =>", args_str.join(", "))
239 };
240
241 if let Expr::DoBlock { .. } = &body.node {
243 let body_formatted = format_expr_impl(body, max_cols, indent);
244 return format!("{} {}", args_part, body_formatted);
245 }
246
247 let single_line_body = format_expr_impl(body, max_cols, indent);
249 let single_line = format!("{} {}", args_part, single_line_body);
250
251 if !single_line.contains('\n') && indent + single_line.len() <= max_cols {
253 return single_line;
254 }
255
256 let body_indent = indent + INDENT_SIZE;
258 format!(
259 "{}\n{}{}",
260 args_part,
261 make_indent(body_indent),
262 format_expr_impl(body, max_cols, body_indent)
263 )
264}
265
266fn format_conditional_multiline(
268 condition: &SpannedExpr,
269 then_expr: &SpannedExpr,
270 else_expr: &SpannedExpr,
271 max_cols: usize,
272 indent: usize,
273) -> String {
274 let cond_str = format_expr_impl(condition, max_cols, indent);
275
276 let if_then_prefix = format!("if {} then", cond_str);
278
279 let inner_indent = indent + INDENT_SIZE;
280
281 if indent + if_then_prefix.len() <= max_cols {
282 if let Expr::Conditional {
285 condition: else_cond,
286 then_expr: else_then,
287 else_expr: else_else,
288 } = &else_expr.node
289 {
290 let else_if_part =
292 format_conditional_multiline(else_cond, else_then, else_else, max_cols, indent);
293 format!(
294 "{}\n{}{}\n{}else {}",
295 if_then_prefix,
296 make_indent(inner_indent),
297 format_expr_impl(then_expr, max_cols, inner_indent),
298 make_indent(indent),
299 else_if_part
300 )
301 } else {
302 format!(
303 "{}\n{}{}\n{}else\n{}{}",
304 if_then_prefix,
305 make_indent(inner_indent),
306 format_expr_impl(then_expr, max_cols, inner_indent),
307 make_indent(indent),
308 make_indent(inner_indent),
309 format_expr_impl(else_expr, max_cols, inner_indent)
310 )
311 }
312 } else {
313 if let Expr::Conditional {
316 condition: else_cond,
317 then_expr: else_then,
318 else_expr: else_else,
319 } = &else_expr.node
320 {
321 let else_if_part =
322 format_conditional_multiline(else_cond, else_then, else_else, max_cols, indent);
323 format!(
324 "if\n{}{}\n{}then\n{}{}\n{}else {}",
325 make_indent(inner_indent),
326 format_expr_impl(condition, max_cols, inner_indent),
327 make_indent(indent),
328 make_indent(inner_indent),
329 format_expr_impl(then_expr, max_cols, inner_indent),
330 make_indent(indent),
331 else_if_part
332 )
333 } else {
334 format!(
335 "if\n{}{}\n{}then\n{}{}\n{}else\n{}{}",
336 make_indent(inner_indent),
337 format_expr_impl(condition, max_cols, inner_indent),
338 make_indent(indent),
339 make_indent(inner_indent),
340 format_expr_impl(then_expr, max_cols, inner_indent),
341 make_indent(indent),
342 make_indent(inner_indent),
343 format_expr_impl(else_expr, max_cols, inner_indent)
344 )
345 }
346 }
347}
348
349fn format_call_multiline(
351 func: &SpannedExpr,
352 args: &[SpannedExpr],
353 max_cols: usize,
354 indent: usize,
355) -> String {
356 let func_str = match &func.node {
357 Expr::Lambda { .. } => format!("({})", format_expr_impl(func, max_cols, indent)),
358 _ => format_expr_impl(func, max_cols, indent),
359 };
360
361 if args.is_empty() {
362 return format!("{}()", func_str);
363 }
364
365 let inner_indent = indent + INDENT_SIZE;
367 let indent_str = make_indent(inner_indent);
368
369 let mut result = format!("{}(", func_str);
370
371 for (i, arg) in args.iter().enumerate() {
372 result.push('\n');
373 result.push_str(&indent_str);
374 result.push_str(&format_expr_impl(arg, max_cols, inner_indent));
375
376 if i < args.len() - 1 {
377 result.push(',');
378 } else {
379 result.push(',');
381 }
382 }
383
384 result.push('\n');
385 result.push_str(&make_indent(indent));
386 result.push(')');
387
388 result
389}
390
391fn format_binary_op_multiline(
393 op: &BinaryOp,
394 left: &SpannedExpr,
395 right: &SpannedExpr,
396 max_cols: usize,
397 indent: usize,
398) -> String {
399 let op_str = binary_op_str(op);
400
401 let left_needs_parens = needs_parens_in_binop(op, left, true);
403 let right_needs_parens = needs_parens_in_binop(op, right, false);
404
405 let left_str = format_expr_impl(left, max_cols, indent);
406 let left_str = if left_needs_parens {
407 format!("({})", left_str)
408 } else {
409 left_str
410 };
411
412 if matches!(op, BinaryOp::Via | BinaryOp::Into | BinaryOp::Where)
415 && let Expr::Lambda { .. } = &right.node
416 {
417 let right_str = format_expr_impl(right, max_cols, indent);
419 let right_str = if right_needs_parens {
420 format!("({})", right_str)
421 } else {
422 right_str
423 };
424
425 let first_line_of_right = right_str.lines().next().unwrap_or(&right_str);
428 let first_line_combined = format!("{} {} {}", left_str, op_str, first_line_of_right);
429
430 if indent + first_line_combined.len() <= max_cols {
431 if right_str.contains('\n') {
434 let remaining_lines = right_str.lines().skip(1).collect::<Vec<_>>().join("\n");
436 return format!(
437 "{} {} {}\n{}",
438 left_str, op_str, first_line_of_right, remaining_lines
439 );
440 } else {
441 return format!("{} {} {}", left_str, op_str, right_str);
443 }
444 }
445
446 let continued_indent = indent;
448 let right_formatted = format_expr_impl(right, max_cols, continued_indent);
449 let right_formatted = if right_needs_parens {
450 format!("({})", right_formatted)
451 } else {
452 right_formatted
453 };
454 return format!(
455 "{}\n{}{} {}",
456 left_str,
457 make_indent(continued_indent),
458 op_str,
459 right_formatted
460 );
461 }
462
463 let right_indent = indent + INDENT_SIZE;
465 let right_str = format_expr_impl(right, max_cols, right_indent);
466 let right_str = if right_needs_parens {
467 format!("({})", right_str)
468 } else {
469 right_str
470 };
471 format!(
472 "{}\n{}{} {}",
473 left_str,
474 make_indent(right_indent),
475 op_str,
476 right_str
477 )
478}
479
480fn format_do_block_multiline(
482 statements: &[DoStatement],
483 return_expr: &SpannedExpr,
484 max_cols: usize,
485 indent: usize,
486) -> String {
487 let inner_indent = indent + INDENT_SIZE;
488 let indent_str = make_indent(inner_indent);
489
490 let mut result = "do {".to_string();
491
492 for stmt in statements {
493 match stmt {
494 DoStatement::Expression(e) => {
495 result.push('\n');
496 result.push_str(&indent_str);
497 result.push_str(&format_expr_impl(e, max_cols, inner_indent));
498 }
499 DoStatement::Comment(c) => {
500 result.push('\n');
501 result.push_str(&indent_str);
502 result.push_str(c);
503 }
504 }
505 }
506
507 result.push('\n');
508 result.push_str(&indent_str);
509 result.push_str("return ");
510 result.push_str(&format_expr_impl(return_expr, max_cols, inner_indent));
511 result.push('\n');
512 result.push_str(&make_indent(indent));
513 result.push('}');
514
515 result
516}
517
518fn lambda_arg_to_str(arg: &LambdaArg) -> String {
520 match arg {
521 LambdaArg::Required(name) => name.clone(),
522 LambdaArg::Optional(name) => format!("{}?", name),
523 LambdaArg::Rest(name) => format!("...{}", name),
524 }
525}
526
527fn binary_op_str(op: &BinaryOp) -> &'static str {
529 match op {
530 BinaryOp::Add => "+",
531 BinaryOp::Subtract => "-",
532 BinaryOp::Multiply => "*",
533 BinaryOp::Divide => "/",
534 BinaryOp::Modulo => "%",
535 BinaryOp::Power => "^",
536 BinaryOp::Equal => "==",
537 BinaryOp::NotEqual => "!=",
538 BinaryOp::Less => "<",
539 BinaryOp::LessEq => "<=",
540 BinaryOp::Greater => ">",
541 BinaryOp::GreaterEq => ">=",
542 BinaryOp::DotEqual => ".==",
543 BinaryOp::DotNotEqual => ".!=",
544 BinaryOp::DotLess => ".<",
545 BinaryOp::DotLessEq => ".<=",
546 BinaryOp::DotGreater => ".>",
547 BinaryOp::DotGreaterEq => ".>=",
548 BinaryOp::And => "&&",
549 BinaryOp::NaturalAnd => "and",
550 BinaryOp::Or => "||",
551 BinaryOp::NaturalOr => "or",
552 BinaryOp::Via => "via",
553 BinaryOp::Into => "into",
554 BinaryOp::Where => "where",
555 BinaryOp::Coalesce => "??",
556 }
557}
558
559fn make_indent(indent: usize) -> String {
561 " ".repeat(indent)
562}
563
564pub fn join_statements_with_spacing(
567 statements: &[(String, usize, usize)], ) -> String {
569 if statements.is_empty() {
570 return String::new();
571 }
572
573 let mut result = String::new();
574
575 for (i, (stmt, _start_line, end_line)) in statements.iter().enumerate() {
576 result.push_str(stmt);
577
578 if i < statements.len() - 1 {
580 let next_start_line = statements[i + 1].1;
581
582 let line_gap = next_start_line.saturating_sub(*end_line).saturating_sub(1);
587
588 let newlines = std::cmp::min(line_gap + 1, 3);
594
595 for _ in 0..newlines {
596 result.push('\n');
597 }
598 }
599 }
600
601 result
602}
603
604#[cfg(test)]
605mod tests {
606 use super::*;
607 use crate::expressions::pairs_to_expr;
608 use crate::parser::get_pairs;
609
610 fn parse_test_expr(source: &str) -> SpannedExpr {
611 use crate::parser::Rule;
612
613 let pairs = get_pairs(source).unwrap();
614
615 for pair in pairs {
617 if pair.as_rule() == Rule::statement {
618 if let Some(inner_pair) = pair.into_inner().next() {
619 return pairs_to_expr(inner_pair.into_inner()).unwrap();
620 }
621 }
622 }
623
624 panic!("No statement found in parsed input");
625 }
626
627 #[test]
628 fn test_format_short_list() {
629 let expr = parse_test_expr("[1, 2, 3]");
630 let formatted = format_expr(&expr, Some(80));
631 assert_eq!(formatted, "[1, 2, 3]");
632 }
633
634 #[test]
635 fn test_format_long_list() {
636 let expr = parse_test_expr("[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]");
637 let formatted = format_expr(&expr, Some(40));
638 assert!(formatted.contains("\n"));
639 assert!(formatted.contains("[\n"));
640 }
641
642 #[test]
643 fn test_format_short_record() {
644 let expr = parse_test_expr("{x: 1, y: 2}");
645 let formatted = format_expr(&expr, Some(80));
646 assert_eq!(formatted, "{x: 1, y: 2}");
647 }
648
649 #[test]
650 fn test_format_long_record() {
651 let expr = parse_test_expr(
652 "{name: \"Alice\", age: 30, email: \"alice@example.com\", address: \"123 Main St\"}",
653 );
654 let formatted = format_expr(&expr, Some(40));
655 assert!(formatted.contains("\n"));
656 assert!(formatted.contains("{\n"));
657 }
658
659 #[test]
660 fn test_format_conditional() {
661 let expr =
662 parse_test_expr("if very_long_condition_variable > 100 then \"yes\" else \"no\"");
663 let formatted = format_expr(&expr, Some(30));
664 assert!(formatted.contains("\n"));
665 }
666
667 #[test]
668 fn test_format_binary_op() {
669 let expr = parse_test_expr("very_long_variable_name + another_very_long_variable_name");
670 let formatted = format_expr(&expr, Some(30));
671 assert!(formatted.contains("\n"));
672 }
673
674 #[test]
675 fn test_format_lambda() {
676 let expr = parse_test_expr("(x, y) => x + y");
677 let formatted = format_expr(&expr, Some(80));
678 assert_eq!(formatted, "(x, y) => x + y");
679 }
680
681 #[test]
682 fn test_format_nested_list() {
683 let expr = parse_test_expr("[[1, 2, 3], [4, 5, 6], [7, 8, 9]]");
684 let formatted = format_expr(&expr, Some(20));
685 assert!(formatted.contains("\n"));
686 }
687
688 #[test]
689 fn test_format_function_call() {
690 let expr = parse_test_expr("map([1, 2, 3], x => x * 2)");
691 let formatted = format_expr(&expr, Some(80));
692 assert_eq!(formatted, "map([1, 2, 3], x => x * 2)");
693 }
694
695 #[test]
696 fn test_format_do_block() {
697 let expr = parse_test_expr("do { x = 1\n return x }");
698 let formatted = format_expr(&expr, Some(80));
699 assert!(formatted.contains("do {"));
700 assert!(formatted.contains("return"));
701 }
702
703 #[test]
704 fn test_multiple_statements() {
705 use crate::parser::Rule;
706
707 let source = "x = [1, 2, 3, 4, 5]\ny = {name: \"Alice\", age: 30}\nz = x + y";
708 let pairs = get_pairs(source).unwrap();
709
710 let mut formatted_statements = Vec::new();
711
712 for pair in pairs {
713 if pair.as_rule() == Rule::statement {
714 if let Some(inner_pair) = pair.into_inner().next() {
715 if let Ok(expr) = pairs_to_expr(inner_pair.into_inner()) {
716 let formatted = format_expr(&expr, Some(80));
717 formatted_statements.push(formatted);
718 }
719 }
720 }
721 }
722
723 assert_eq!(formatted_statements.len(), 3);
725 assert_eq!(formatted_statements[0], "x = [1, 2, 3, 4, 5]");
726 assert_eq!(formatted_statements[1], "y = {name: \"Alice\", age: 30}");
727 assert_eq!(formatted_statements[2], "z = x + y");
728 }
729
730 #[test]
731 fn test_comments_are_preserved() {
732 use crate::parser::Rule;
733
734 let source = "// Comment 1\nx = [1, 2, 3]\n// Comment 2\ny = x + 1";
735 let pairs = get_pairs(source).unwrap();
736
737 let mut formatted_statements = Vec::new();
738
739 for pair in pairs {
740 if pair.as_rule() == Rule::statement {
741 if let Some(inner_pair) = pair.into_inner().next() {
742 match inner_pair.as_rule() {
743 Rule::comment => {
744 formatted_statements.push(inner_pair.as_str().to_string());
746 }
747 _ => {
748 if let Ok(expr) = pairs_to_expr(inner_pair.into_inner()) {
750 let formatted = format_expr(&expr, Some(80));
751 formatted_statements.push(formatted);
752 }
753 }
754 }
755 }
756 }
757 }
758
759 assert_eq!(formatted_statements.len(), 4);
761 assert_eq!(formatted_statements[0], "// Comment 1");
762 assert_eq!(formatted_statements[1], "x = [1, 2, 3]");
763 assert_eq!(formatted_statements[2], "// Comment 2");
764 assert_eq!(formatted_statements[3], "y = x + 1");
765 }
766
767 #[test]
768 fn test_comments_with_formatted_code() {
769 use crate::parser::Rule;
770
771 let source = "// Configuration\nconfig = {name: \"test\", debug: true}\n// Process data\nresult = [1, 2, 3]";
772 let pairs = get_pairs(source).unwrap();
773
774 let mut formatted_statements = Vec::new();
775
776 for pair in pairs {
777 if pair.as_rule() == Rule::statement {
778 if let Some(inner_pair) = pair.into_inner().next() {
779 match inner_pair.as_rule() {
780 Rule::comment => {
781 formatted_statements.push(inner_pair.as_str().to_string());
782 }
783 _ => {
784 if let Ok(expr) = pairs_to_expr(inner_pair.into_inner()) {
785 let formatted = format_expr(&expr, Some(80));
786 formatted_statements.push(formatted);
787 }
788 }
789 }
790 }
791 }
792 }
793
794 let result = formatted_statements.join("\n");
795
796 assert_eq!(formatted_statements.len(), 4);
798 assert_eq!(formatted_statements[0], "// Configuration");
799 assert!(formatted_statements[1].starts_with("config = "));
800 assert_eq!(formatted_statements[2], "// Process data");
801 assert_eq!(formatted_statements[3], "result = [1, 2, 3]");
802
803 assert!(result.contains("// Configuration"));
805 assert!(result.contains("// Process data"));
806 }
807
808 #[test]
809 fn test_output_declaration_with_assignment() {
810 use crate::parser::Rule;
811
812 let source = "output result = 42";
813 let pairs = get_pairs(source).unwrap();
814
815 let mut formatted_statements = Vec::new();
816
817 for pair in pairs {
818 if pair.as_rule() == Rule::statement {
819 if let Some(inner_pair) = pair.into_inner().next() {
820 match inner_pair.as_rule() {
821 Rule::output_declaration => {
822 let mut inner = inner_pair.into_inner();
824 let assignment_or_ident = inner.next().unwrap();
825
826 let formatted = if assignment_or_ident.as_rule() == Rule::assignment {
827 let mut assignment_inner = assignment_or_ident.into_inner();
829 let ident = assignment_inner.next().unwrap().as_str();
830 let value_expr =
831 pairs_to_expr(assignment_inner.next().unwrap().into_inner())
832 .unwrap();
833 let value_formatted = format_expr(&value_expr, Some(80));
834 format!("{} = {}", ident, value_formatted)
835 } else {
836 assignment_or_ident.as_str().to_string()
837 };
838
839 formatted_statements.push(format!("output {}", formatted));
840 }
841 _ => {
842 if let Ok(expr) = pairs_to_expr(inner_pair.into_inner()) {
843 let formatted = format_expr(&expr, Some(80));
844 formatted_statements.push(formatted);
845 }
846 }
847 }
848 }
849 }
850 }
851
852 assert_eq!(formatted_statements.len(), 1);
853 assert_eq!(formatted_statements[0], "output result = 42");
854 }
855
856 #[test]
857 fn test_output_statement_separate() {
858 use crate::parser::Rule;
859
860 let source = "result = 42\noutput result";
861 let pairs = get_pairs(source).unwrap();
862
863 let mut formatted_statements = Vec::new();
864
865 for pair in pairs {
866 if pair.as_rule() == Rule::statement {
867 if let Some(inner_pair) = pair.into_inner().next() {
868 match inner_pair.as_rule() {
869 Rule::output_declaration => {
870 let mut inner = inner_pair.into_inner();
872 let assignment_or_ident = inner.next().unwrap();
873
874 let formatted = if assignment_or_ident.as_rule() == Rule::assignment {
875 let mut assignment_inner = assignment_or_ident.into_inner();
877 let ident = assignment_inner.next().unwrap().as_str();
878 let value_expr =
879 pairs_to_expr(assignment_inner.next().unwrap().into_inner())
880 .unwrap();
881 let value_formatted = format_expr(&value_expr, Some(80));
882 format!("{} = {}", ident, value_formatted)
883 } else {
884 assignment_or_ident.as_str().to_string()
885 };
886
887 formatted_statements.push(format!("output {}", formatted));
888 }
889 _ => {
890 if let Ok(expr) = pairs_to_expr(inner_pair.into_inner()) {
891 let formatted = format_expr(&expr, Some(80));
892 formatted_statements.push(formatted);
893 }
894 }
895 }
896 }
897 }
898 }
899
900 assert_eq!(formatted_statements.len(), 2);
901 assert_eq!(formatted_statements[0], "result = 42");
902 assert_eq!(formatted_statements[1], "output result");
903 }
904
905 #[test]
906 fn test_output_with_complex_expression() {
907 use crate::parser::Rule;
908
909 let source = "output total = [1, 2, 3] into sum";
910 let pairs = get_pairs(source).unwrap();
911
912 let mut formatted_statements = Vec::new();
913
914 for pair in pairs {
915 if pair.as_rule() == Rule::statement {
916 if let Some(inner_pair) = pair.into_inner().next() {
917 match inner_pair.as_rule() {
918 Rule::output_declaration => {
919 let mut inner = inner_pair.into_inner();
921 let assignment_or_ident = inner.next().unwrap();
922
923 let formatted = if assignment_or_ident.as_rule() == Rule::assignment {
924 let mut assignment_inner = assignment_or_ident.into_inner();
926 let ident = assignment_inner.next().unwrap().as_str();
927 let value_expr =
928 pairs_to_expr(assignment_inner.next().unwrap().into_inner())
929 .unwrap();
930 let value_formatted = format_expr(&value_expr, Some(80));
931 format!("{} = {}", ident, value_formatted)
932 } else {
933 assignment_or_ident.as_str().to_string()
934 };
935
936 formatted_statements.push(format!("output {}", formatted));
937 }
938 _ => {
939 if let Ok(expr) = pairs_to_expr(inner_pair.into_inner()) {
940 let formatted = format_expr(&expr, Some(80));
941 formatted_statements.push(formatted);
942 }
943 }
944 }
945 }
946 }
947 }
948
949 assert_eq!(formatted_statements.len(), 1);
950 assert_eq!(formatted_statements[0], "output total = [1, 2, 3] into sum");
951 }
952
953 #[test]
954 fn test_end_of_line_comments() {
955 use crate::parser::Rule;
956
957 let source = "x = 5 // this is an end-of-line comment\ny = 10";
958 let pairs = get_pairs(source).unwrap();
959
960 let mut formatted_statements = Vec::new();
961
962 for pair in pairs {
963 if pair.as_rule() == Rule::statement {
964 let mut inner_pairs = pair.into_inner();
965
966 if let Some(first_pair) = inner_pairs.next() {
967 let formatted = match first_pair.as_rule() {
968 Rule::comment => first_pair.as_str().to_string(),
969 _ => {
970 if let Ok(expr) = pairs_to_expr(first_pair.into_inner()) {
971 format_expr(&expr, Some(80))
972 } else {
973 continue;
974 }
975 }
976 };
977
978 if let Some(eol_comment) = inner_pairs.next() {
980 if eol_comment.as_rule() == Rule::comment {
981 formatted_statements.push(format!(
982 "{} {}",
983 formatted,
984 eol_comment.as_str()
985 ));
986 } else {
987 formatted_statements.push(formatted);
988 }
989 } else {
990 formatted_statements.push(formatted);
991 }
992 }
993 }
994 }
995
996 assert_eq!(formatted_statements.len(), 2);
997 assert_eq!(
998 formatted_statements[0],
999 "x = 5 // this is an end-of-line comment"
1000 );
1001 assert_eq!(formatted_statements[1], "y = 10");
1002 }
1003
1004 #[test]
1005 fn test_multiple_end_of_line_comments() {
1006 use crate::parser::Rule;
1007
1008 let source = "a = 1 // comment 1\nb = 2 // comment 2\nc = 3";
1009 let pairs = get_pairs(source).unwrap();
1010
1011 let mut formatted_statements = Vec::new();
1012
1013 for pair in pairs {
1014 if pair.as_rule() == Rule::statement {
1015 let mut inner_pairs = pair.into_inner();
1016
1017 if let Some(first_pair) = inner_pairs.next() {
1018 let formatted = match first_pair.as_rule() {
1019 Rule::comment => first_pair.as_str().to_string(),
1020 _ => {
1021 if let Ok(expr) = pairs_to_expr(first_pair.into_inner()) {
1022 format_expr(&expr, Some(80))
1023 } else {
1024 continue;
1025 }
1026 }
1027 };
1028
1029 if let Some(eol_comment) = inner_pairs.next() {
1031 if eol_comment.as_rule() == Rule::comment {
1032 formatted_statements.push(format!(
1033 "{} {}",
1034 formatted,
1035 eol_comment.as_str()
1036 ));
1037 } else {
1038 formatted_statements.push(formatted);
1039 }
1040 } else {
1041 formatted_statements.push(formatted);
1042 }
1043 }
1044 }
1045 }
1046
1047 assert_eq!(formatted_statements.len(), 3);
1048 assert_eq!(formatted_statements[0], "a = 1 // comment 1");
1049 assert_eq!(formatted_statements[1], "b = 2 // comment 2");
1050 assert_eq!(formatted_statements[2], "c = 3");
1051 }
1052
1053 #[test]
1054 fn test_eol_comments_not_joined_with_next_line() {
1055 use crate::parser::Rule;
1056
1057 let source = "x = 1 // first value\ny = 2 // second value\nz = x + y";
1059 let pairs = get_pairs(source).unwrap();
1060
1061 let mut formatted_statements = Vec::new();
1062
1063 for pair in pairs {
1064 if pair.as_rule() == Rule::statement {
1065 let mut inner_pairs = pair.into_inner();
1066
1067 if let Some(first_pair) = inner_pairs.next() {
1068 let formatted = match first_pair.as_rule() {
1069 Rule::comment => first_pair.as_str().to_string(),
1070 _ => {
1071 if let Ok(expr) = pairs_to_expr(first_pair.into_inner()) {
1072 format_expr(&expr, Some(80))
1073 } else {
1074 continue;
1075 }
1076 }
1077 };
1078
1079 if let Some(eol_comment) = inner_pairs.next() {
1081 if eol_comment.as_rule() == Rule::comment {
1082 formatted_statements.push(format!(
1083 "{} {}",
1084 formatted,
1085 eol_comment.as_str()
1086 ));
1087 } else {
1088 formatted_statements.push(formatted);
1089 }
1090 } else {
1091 formatted_statements.push(formatted);
1092 }
1093 }
1094 }
1095 }
1096
1097 let result = formatted_statements.join("\n");
1099
1100 assert_eq!(formatted_statements.len(), 3);
1101 assert_eq!(result.lines().count(), 3);
1103 assert_eq!(formatted_statements[0], "x = 1 // first value");
1104 assert_eq!(formatted_statements[1], "y = 2 // second value");
1105 assert_eq!(formatted_statements[2], "z = x + y");
1106
1107 for line in result.lines() {
1109 assert_eq!(
1111 line.matches('=').count(),
1112 1,
1113 "Line should not contain multiple statements: {}",
1114 line
1115 );
1116 }
1117 }
1118
1119 #[test]
1120 fn test_eol_comments_with_line_breaking() {
1121 use crate::parser::Rule;
1122
1123 let source = "longRecord = {name: \"Alice\", age: 30, email: \"alice@example.com\", address: \"123 Main St\"} // user data";
1125 let pairs = get_pairs(source).unwrap();
1126
1127 let mut formatted_statements = Vec::new();
1128
1129 for pair in pairs {
1130 if pair.as_rule() == Rule::statement {
1131 let mut inner_pairs = pair.into_inner();
1132
1133 if let Some(first_pair) = inner_pairs.next() {
1134 let formatted = match first_pair.as_rule() {
1135 Rule::comment => first_pair.as_str().to_string(),
1136 _ => {
1137 if let Ok(expr) = pairs_to_expr(first_pair.into_inner()) {
1138 format_expr(&expr, Some(40))
1140 } else {
1141 continue;
1142 }
1143 }
1144 };
1145
1146 if let Some(eol_comment) = inner_pairs.next() {
1148 if eol_comment.as_rule() == Rule::comment {
1149 formatted_statements.push(format!(
1150 "{} {}",
1151 formatted,
1152 eol_comment.as_str()
1153 ));
1154 } else {
1155 formatted_statements.push(formatted);
1156 }
1157 } else {
1158 formatted_statements.push(formatted);
1159 }
1160 }
1161 }
1162 }
1163
1164 assert_eq!(formatted_statements.len(), 1);
1165 let result = &formatted_statements[0];
1166
1167 assert!(result.ends_with("// user data"));
1169 assert!(result.contains("longRecord = "));
1171 }
1172
1173 #[test]
1174 fn test_actual_line_breaking_behavior() {
1175 use crate::parser::Rule;
1176
1177 let source = "x = {name: \"Alice\", age: 30, email: \"alice@example.com\", address: \"123 Main St\", city: \"Springfield\"}";
1179 let pairs = get_pairs(source).unwrap();
1180
1181 for pair in pairs {
1182 if pair.as_rule() == Rule::statement {
1183 if let Some(inner_pair) = pair.into_inner().next() {
1184 if let Ok(expr) = pairs_to_expr(inner_pair.into_inner()) {
1185 let formatted = format_expr(&expr, Some(40));
1186 println!("Formatted output:\n{}", formatted);
1187 println!("Line count: {}", formatted.lines().count());
1188
1189 if formatted.lines().count() > 1 {
1191 println!("✓ Lines were broken");
1192 } else {
1193 println!(
1194 "✗ No line breaking occurred - output is {} chars",
1195 formatted.len()
1196 );
1197 }
1198 }
1199 }
1200 }
1201 }
1202 }
1203
1204 #[test]
1205 fn test_multiline_input_gets_collapsed() {
1206 use crate::parser::Rule;
1207
1208 let source = "x = [\n 1,\n 2,\n 3\n]";
1210 let pairs = get_pairs(source).unwrap();
1211
1212 for pair in pairs {
1213 if pair.as_rule() == Rule::statement {
1214 if let Some(inner_pair) = pair.into_inner().next() {
1215 if let Ok(expr) = pairs_to_expr(inner_pair.into_inner()) {
1216 let formatted = format_expr(&expr, Some(80));
1217 println!("Input:\n{}", source);
1218 println!("Output:\n{}", formatted);
1219
1220 assert_eq!(formatted, "x = [1, 2, 3]");
1222 }
1223 }
1224 }
1225 }
1226 }
1227
1228 #[test]
1229 fn test_empty_lines_between_statements() {
1230 use crate::parser::Rule;
1231
1232 let source = "x = 1\n\ny = 2\n\n\n\nz = 3";
1234 let pairs = get_pairs(source).unwrap();
1235
1236 let mut statements_with_positions = Vec::new();
1237
1238 for pair in pairs {
1239 if pair.as_rule() == Rule::statement {
1240 let start_line = pair.as_span().start_pos().line_col().0;
1241 let end_line = pair.as_span().end_pos().line_col().0;
1242
1243 if let Some(inner_pair) = pair.into_inner().next() {
1244 if let Ok(expr) = pairs_to_expr(inner_pair.into_inner()) {
1245 let formatted = format_expr(&expr, Some(80));
1246 statements_with_positions.push((formatted, start_line, end_line));
1247 }
1248 }
1249 }
1250 }
1251
1252 println!("Statement positions: {:?}", statements_with_positions);
1253
1254 let result = join_statements_with_spacing(&statements_with_positions);
1256 println!("Output with preserved spacing:\n{}", result);
1257
1258 assert_eq!(result.lines().count(), 6); assert_eq!(result, "x = 1\n\ny = 2\n\n\nz = 3");
1269 }
1270
1271 #[test]
1272 fn test_assignment_with_long_list_indentation() {
1273 use crate::parser::Rule;
1274
1275 let source = "ingredients = [{name: \"sugar\", amount: 1}, {name: \"flour\", amount: 2}]";
1277 let pairs = get_pairs(source).unwrap();
1278
1279 for pair in pairs {
1280 if pair.as_rule() == Rule::statement {
1281 if let Some(inner_pair) = pair.into_inner().next() {
1282 if let Ok(expr) = pairs_to_expr(inner_pair.into_inner()) {
1283 let formatted = format_expr(&expr, Some(40));
1284 println!("Formatted:\n{}", formatted);
1285
1286 assert!(formatted.contains("[\n {"));
1288 assert!(!formatted.contains(" ")); }
1290 }
1291 }
1292 }
1293 }
1294
1295 #[test]
1296 fn test_else_if_chain_stays_flat() {
1297 let source = "x = if a then 1 else if b then 2 else if c then 3 else 4";
1299 let expr = parse_test_expr(source);
1300 let formatted = format_expr(&expr, Some(40));
1301 println!("Formatted:\n{}", formatted);
1302
1303 let else_if_count = formatted.matches("else if").count();
1306 assert_eq!(else_if_count, 2, "Should have 2 'else if' clauses");
1307
1308 for line in formatted.lines() {
1311 let leading_spaces = line.len() - line.trim_start().len();
1312 assert!(
1314 leading_spaces <= 2,
1315 "Line has too much indentation: '{}'",
1316 line
1317 );
1318 }
1319 }
1320
1321 #[test]
1322 fn test_long_else_if_chain() {
1323 let source = "tax = if income <= 10000 then income * 0.1 else if income <= 50000 then 1000 + (income - 10000) * 0.2 else if income <= 100000 then 9000 + (income - 50000) * 0.3 else 24000 + (income - 100000) * 0.4";
1325 let expr = parse_test_expr(source);
1326 let formatted = format_expr(&expr, Some(60));
1327 println!("Formatted:\n{}", formatted);
1328
1329 let else_count = formatted.matches("\nelse").count();
1331 assert!(
1332 else_count >= 3,
1333 "Should have at least 3 else/else-if clauses, found {}",
1334 else_count
1335 );
1336
1337 for line in formatted.lines() {
1339 if line.starts_with("else") {
1340 assert!(
1342 line.starts_with("else"),
1343 "else clause should start at column 0: '{}'",
1344 line
1345 );
1346 }
1347 }
1348 }
1349
1350 #[test]
1351 fn test_multiline_preserves_precedence_parentheses() {
1352 let source = "result = amount * (rate * (1 + rate) ^ n) / ((1 + rate) ^ n - 1)";
1356 let expr = parse_test_expr(source);
1357 let formatted = format_expr(&expr, Some(40));
1358 println!("Formatted:\n{}", formatted);
1359
1360 assert!(
1363 formatted.contains("/ ((1 + rate) ^ n - 1)"),
1364 "Denominator should be wrapped in parentheses to preserve precedence. Got:\n{}",
1365 formatted
1366 );
1367 }
1368
1369 #[test]
1370 fn test_multiline_division_with_subtraction() {
1371 let source = "x = a / (b - c)";
1373 let expr = parse_test_expr(source);
1374 let formatted = format_expr(&expr, Some(80));
1375
1376 assert_eq!(formatted, "x = a / (b - c)");
1377 }
1378
1379 #[test]
1380 fn test_multiline_multiplication_with_addition() {
1381 let source = "x = a * (b + c)";
1383 let expr = parse_test_expr(source);
1384 let formatted = format_expr(&expr, Some(80));
1385
1386 assert_eq!(formatted, "x = a * (b + c)");
1387 }
1388
1389 #[test]
1390 fn test_else_indented_in_lambda_body() {
1391 let source = "is_positive = n => if n > 0 then true else false";
1394 let expr = parse_test_expr(source);
1395 let formatted = format_expr(&expr, Some(30)); for line in formatted.lines() {
1399 if line.trim().starts_with("else") {
1400 let leading_spaces = line.len() - line.trim_start().len();
1401 assert!(
1402 leading_spaces > 0,
1403 "else should be indented in lambda body, got: '{}'",
1404 line
1405 );
1406 }
1407 }
1408
1409 let reparsed = parse_test_expr(&formatted);
1411 assert!(matches!(reparsed.node, Expr::Assignment { .. }));
1412 }
1413
1414 #[test]
1415 fn test_via_breaks_before_operator_not_after() {
1416 let source = "result = [1, 2, 3, 4, 5] via (x) => x * 2";
1419 let expr = parse_test_expr(source);
1420 let formatted = format_expr(&expr, Some(30)); for line in formatted.lines() {
1424 assert!(
1425 !line.trim_end().ends_with("via"),
1426 "via should not be at end of line (would be parse error), got: '{}'",
1427 line
1428 );
1429 }
1430
1431 let reparsed = parse_test_expr(&formatted);
1433 assert!(matches!(reparsed.node, Expr::Assignment { .. }));
1434 }
1435
1436 #[test]
1437 fn test_record_keys_with_spaces_preserve_quotes() {
1438 let source = r#"x = {"my key": 1, "another-key": 2}"#;
1440 let expr = parse_test_expr(source);
1441 let formatted = format_expr(&expr, Some(80));
1442
1443 assert!(
1445 formatted.contains(r#""my key""#),
1446 "Key with spaces should remain quoted, got: {}",
1447 formatted
1448 );
1449 assert!(
1450 formatted.contains(r#""another-key""#),
1451 "Key with dashes should remain quoted, got: {}",
1452 formatted
1453 );
1454
1455 let reparsed = parse_test_expr(&formatted);
1457 assert!(matches!(reparsed.node, Expr::Assignment { .. }));
1458 }
1459
1460 #[test]
1461 fn test_record_keys_valid_identifiers_no_quotes() {
1462 let source = r#"x = {name: "Alice", age: 30}"#;
1464 let expr = parse_test_expr(source);
1465 let formatted = format_expr(&expr, Some(80));
1466
1467 assert_eq!(formatted, r#"x = {name: "Alice", age: 30}"#);
1469 }
1470
1471 #[test]
1472 fn test_record_keys_starting_with_number_need_quotes() {
1473 let source = r#"x = {"123abc": 1}"#;
1475 let expr = parse_test_expr(source);
1476 let formatted = format_expr(&expr, Some(80));
1477
1478 assert!(
1479 formatted.contains(r#""123abc""#),
1480 "Key starting with number should remain quoted, got: {}",
1481 formatted
1482 );
1483
1484 let reparsed = parse_test_expr(&formatted);
1486 assert!(matches!(reparsed.node, Expr::Assignment { .. }));
1487 }
1488}