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 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.iter().map(format_record_entry_single_line).collect();
73 format!("{{{}}}", entries_str.join(", "))
74 }
75 _ => expr_to_source(expr),
77 }
78}
79
80fn format_record_entry_single_line(entry: &RecordEntry) -> String {
82 match &entry.key {
83 RecordKey::Static(key) => format!("{}: {}", key, format_single_line(&entry.value)),
84 RecordKey::Dynamic(key_expr) => {
85 format!("[{}]: {}", format_single_line(key_expr), format_single_line(&entry.value))
86 }
87 RecordKey::Shorthand(name) => name.clone(),
88 RecordKey::Spread(expr) => format_single_line(expr),
89 }
90}
91
92fn format_multiline(expr: &SpannedExpr, max_cols: usize, indent: usize) -> String {
94 match &expr.node {
95 Expr::Output { expr: inner_expr } => {
96 let formatted_inner = format_expr_impl(inner_expr, max_cols, indent);
98 format!("output {}", formatted_inner)
99 }
100 Expr::Assignment { ident, value } => {
101 format_assignment_multiline(ident, value, max_cols, indent)
102 }
103 Expr::List(items) => format_list_multiline(items, max_cols, indent),
104 Expr::Record(entries) => format_record_multiline(entries, max_cols, indent),
105 Expr::Conditional { condition, then_expr, else_expr } => {
106 format_conditional_multiline(condition, then_expr, else_expr, max_cols, indent)
107 }
108 Expr::Call { func, args } => format_call_multiline(func, args, max_cols, indent),
109 Expr::BinaryOp { op, left, right } => {
110 format_binary_op_multiline(op, left, right, max_cols, indent)
111 }
112 Expr::DoBlock { statements, return_expr } => {
113 format_do_block_multiline(statements, return_expr, max_cols, indent)
114 }
115 _ => expr_to_source(expr),
117 }
118}
119
120fn format_assignment_multiline(ident: &str, value: &SpannedExpr, max_cols: usize, indent: usize) -> String {
122 let prefix = format!("{} = ", ident);
127
128 let formatted_value = format_expr_impl(value, max_cols, indent);
131
132 format!("{}{}", prefix, formatted_value)
133}
134
135fn format_list_multiline(items: &[SpannedExpr], max_cols: usize, indent: usize) -> String {
137 if items.is_empty() {
138 return "[]".to_string();
139 }
140
141 let inner_indent = indent + INDENT_SIZE;
142 let indent_str = make_indent(inner_indent);
143
144 let mut result = "[".to_string();
145
146 for item in items.iter() {
147 result.push('\n');
148 result.push_str(&indent_str);
149 result.push_str(&format_expr_impl(item, max_cols, inner_indent));
150
151 result.push(',');
153 }
154
155 result.push('\n');
156 result.push_str(&make_indent(indent));
157 result.push(']');
158
159 result
160}
161
162fn format_record_multiline(entries: &[RecordEntry], max_cols: usize, indent: usize) -> String {
164 if entries.is_empty() {
165 return "{}".to_string();
166 }
167
168 let inner_indent = indent + INDENT_SIZE;
169 let indent_str = make_indent(inner_indent);
170
171 let mut result = "{".to_string();
172
173 for entry in entries {
174 result.push('\n');
175 result.push_str(&indent_str);
176 result.push_str(&format_record_entry(entry, max_cols, inner_indent));
177
178 result.push(',');
180 }
181
182 result.push('\n');
183 result.push_str(&make_indent(indent));
184 result.push('}');
185
186 result
187}
188
189fn format_record_entry(entry: &RecordEntry, max_cols: usize, indent: usize) -> String {
191 match &entry.key {
192 RecordKey::Static(key) => {
193 format!("{}: {}", key, format_expr_impl(&entry.value, max_cols, indent))
194 }
195 RecordKey::Dynamic(key_expr) => {
196 format!(
197 "[{}]: {}",
198 format_expr_impl(key_expr, max_cols, indent),
199 format_expr_impl(&entry.value, max_cols, indent)
200 )
201 }
202 RecordKey::Shorthand(name) => name.clone(),
203 RecordKey::Spread(expr) => format_expr_impl(expr, max_cols, indent),
204 }
205}
206
207fn format_lambda(args: &[LambdaArg], body: &SpannedExpr, max_cols: usize, indent: usize) -> String {
209 let args_str: Vec<String> = args.iter().map(lambda_arg_to_str).collect();
210
211 let args_part = if args.len() == 1 && matches!(args[0], LambdaArg::Required(_)) {
213 format!("{} =>", args_str[0])
214 } else {
215 format!("({}) =>", args_str.join(", "))
216 };
217
218 if let Expr::DoBlock { .. } = &body.node {
220 let body_formatted = format_expr_impl(body, max_cols, indent);
221 return format!("{} {}", args_part, body_formatted);
222 }
223
224 let single_line_body = format_expr_impl(body, max_cols, indent);
226 let single_line = format!("{} {}", args_part, single_line_body);
227
228 if !single_line.contains('\n') && indent + single_line.len() <= max_cols {
230 return single_line;
231 }
232
233 let body_indent = indent + INDENT_SIZE;
235 format!(
236 "{}\n{}{}",
237 args_part,
238 make_indent(body_indent),
239 format_expr_impl(body, max_cols, body_indent)
240 )
241}
242
243fn format_conditional_multiline(
245 condition: &SpannedExpr,
246 then_expr: &SpannedExpr,
247 else_expr: &SpannedExpr,
248 max_cols: usize,
249 indent: usize,
250) -> String {
251 let cond_str = format_expr_impl(condition, max_cols, indent);
252
253 let if_then_prefix = format!("if {} then", cond_str);
255
256 if indent + if_then_prefix.len() <= max_cols {
257 let inner_indent = indent + INDENT_SIZE;
259 format!(
260 "{}\n{}{}\nelse\n{}{}",
261 if_then_prefix,
262 make_indent(inner_indent),
263 format_expr_impl(then_expr, max_cols, inner_indent),
264 make_indent(inner_indent),
265 format_expr_impl(else_expr, max_cols, inner_indent)
266 )
267 } else {
268 let inner_indent = indent + INDENT_SIZE;
270 format!(
271 "if\n{}{}\nthen\n{}{}\nelse\n{}{}",
272 make_indent(inner_indent),
273 format_expr_impl(condition, max_cols, inner_indent),
274 make_indent(inner_indent),
275 format_expr_impl(then_expr, max_cols, inner_indent),
276 make_indent(inner_indent),
277 format_expr_impl(else_expr, max_cols, inner_indent)
278 )
279 }
280}
281
282fn format_call_multiline(func: &SpannedExpr, args: &[SpannedExpr], max_cols: usize, indent: usize) -> String {
284 let func_str = match &func.node {
285 Expr::Lambda { .. } => format!("({})", format_expr_impl(func, max_cols, indent)),
286 _ => format_expr_impl(func, max_cols, indent),
287 };
288
289 if args.is_empty() {
290 return format!("{}()", func_str);
291 }
292
293 let inner_indent = indent + INDENT_SIZE;
295 let indent_str = make_indent(inner_indent);
296
297 let mut result = format!("{}(", func_str);
298
299 for (i, arg) in args.iter().enumerate() {
300 result.push('\n');
301 result.push_str(&indent_str);
302 result.push_str(&format_expr_impl(arg, max_cols, inner_indent));
303
304 if i < args.len() - 1 {
305 result.push(',');
306 } else {
307 result.push(',');
309 }
310 }
311
312 result.push('\n');
313 result.push_str(&make_indent(indent));
314 result.push(')');
315
316 result
317}
318
319fn format_binary_op_multiline(
321 op: &BinaryOp,
322 left: &SpannedExpr,
323 right: &SpannedExpr,
324 max_cols: usize,
325 indent: usize,
326) -> String {
327 let op_str = binary_op_str(op);
328 let left_str = format_expr_impl(left, max_cols, indent);
329
330 if matches!(op, BinaryOp::Via | BinaryOp::Into | BinaryOp::Where)
333 && let Expr::Lambda { .. } = &right.node {
334 let right_str = format_expr_impl(right, max_cols, indent);
336
337 let first_line_of_right = right_str.lines().next().unwrap_or(&right_str);
340 let first_line_combined = format!("{} {} {}", left_str, op_str, first_line_of_right);
341
342 if indent + first_line_combined.len() <= max_cols {
343 if right_str.contains('\n') {
346 let remaining_lines = right_str.lines().skip(1).collect::<Vec<_>>().join("\n");
348 return format!("{} {} {}\n{}", left_str, op_str, first_line_of_right, remaining_lines);
349 } else {
350 return format!("{} {} {}", left_str, op_str, right_str);
352 }
353 }
354
355 let continued_indent = indent;
357 let right_formatted = format_expr_impl(right, max_cols, continued_indent);
358 return format!("{} {}\n{}{}", left_str, op_str, make_indent(continued_indent), right_formatted);
359 }
360
361 let right_indent = indent + INDENT_SIZE;
363 format!(
364 "{}\n{}{} {}",
365 left_str,
366 make_indent(right_indent),
367 op_str,
368 format_expr_impl(right, max_cols, right_indent)
369 )
370}
371
372fn format_do_block_multiline(statements: &[DoStatement], return_expr: &SpannedExpr, max_cols: usize, indent: usize) -> String {
374 let inner_indent = indent + INDENT_SIZE;
375 let indent_str = make_indent(inner_indent);
376
377 let mut result = "do {".to_string();
378
379 for stmt in statements {
380 match stmt {
381 DoStatement::Expression(e) => {
382 result.push('\n');
383 result.push_str(&indent_str);
384 result.push_str(&format_expr_impl(e, max_cols, inner_indent));
385 }
386 DoStatement::Comment(c) => {
387 result.push('\n');
388 result.push_str(&indent_str);
389 result.push_str(c);
390 }
391 }
392 }
393
394 result.push('\n');
395 result.push_str(&indent_str);
396 result.push_str("return ");
397 result.push_str(&format_expr_impl(return_expr, max_cols, inner_indent));
398 result.push('\n');
399 result.push_str(&make_indent(indent));
400 result.push('}');
401
402 result
403}
404
405fn lambda_arg_to_str(arg: &LambdaArg) -> String {
407 match arg {
408 LambdaArg::Required(name) => name.clone(),
409 LambdaArg::Optional(name) => format!("{}?", name),
410 LambdaArg::Rest(name) => format!("...{}", name),
411 }
412}
413
414fn binary_op_str(op: &BinaryOp) -> &'static str {
416 match op {
417 BinaryOp::Add => "+",
418 BinaryOp::Subtract => "-",
419 BinaryOp::Multiply => "*",
420 BinaryOp::Divide => "/",
421 BinaryOp::Modulo => "%",
422 BinaryOp::Power => "^",
423 BinaryOp::Equal => "==",
424 BinaryOp::NotEqual => "!=",
425 BinaryOp::Less => "<",
426 BinaryOp::LessEq => "<=",
427 BinaryOp::Greater => ">",
428 BinaryOp::GreaterEq => ">=",
429 BinaryOp::DotEqual => ".==",
430 BinaryOp::DotNotEqual => ".!=",
431 BinaryOp::DotLess => ".<",
432 BinaryOp::DotLessEq => ".<=",
433 BinaryOp::DotGreater => ".>",
434 BinaryOp::DotGreaterEq => ".>=",
435 BinaryOp::And => "&&",
436 BinaryOp::NaturalAnd => "and",
437 BinaryOp::Or => "||",
438 BinaryOp::NaturalOr => "or",
439 BinaryOp::Via => "via",
440 BinaryOp::Into => "into",
441 BinaryOp::Where => "where",
442 BinaryOp::Coalesce => "??",
443 }
444}
445
446fn make_indent(indent: usize) -> String {
448 " ".repeat(indent)
449}
450
451pub fn join_statements_with_spacing(
454 statements: &[(String, usize, usize)], ) -> String {
456 if statements.is_empty() {
457 return String::new();
458 }
459
460 let mut result = String::new();
461
462 for (i, (stmt, _start_line, end_line)) in statements.iter().enumerate() {
463 result.push_str(stmt);
464
465 if i < statements.len() - 1 {
467 let next_start_line = statements[i + 1].1;
468
469 let line_gap = next_start_line.saturating_sub(*end_line).saturating_sub(1);
474
475 let newlines = std::cmp::min(line_gap + 1, 3);
481
482 for _ in 0..newlines {
483 result.push('\n');
484 }
485 }
486 }
487
488 result
489}
490
491#[cfg(test)]
492mod tests {
493 use super::*;
494 use crate::parser::get_pairs;
495 use crate::expressions::pairs_to_expr;
496
497 fn parse_test_expr(source: &str) -> SpannedExpr {
498 use crate::parser::Rule;
499
500 let pairs = get_pairs(source).unwrap();
501
502 for pair in pairs {
504 if pair.as_rule() == Rule::statement {
505 if let Some(inner_pair) = pair.into_inner().next() {
506 return pairs_to_expr(inner_pair.into_inner()).unwrap();
507 }
508 }
509 }
510
511 panic!("No statement found in parsed input");
512 }
513
514 #[test]
515 fn test_format_short_list() {
516 let expr = parse_test_expr("[1, 2, 3]");
517 let formatted = format_expr(&expr, Some(80));
518 assert_eq!(formatted, "[1, 2, 3]");
519 }
520
521 #[test]
522 fn test_format_long_list() {
523 let expr = parse_test_expr("[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]");
524 let formatted = format_expr(&expr, Some(40));
525 assert!(formatted.contains("\n"));
526 assert!(formatted.contains("[\n"));
527 }
528
529 #[test]
530 fn test_format_short_record() {
531 let expr = parse_test_expr("{x: 1, y: 2}");
532 let formatted = format_expr(&expr, Some(80));
533 assert_eq!(formatted, "{x: 1, y: 2}");
534 }
535
536 #[test]
537 fn test_format_long_record() {
538 let expr = parse_test_expr("{name: \"Alice\", age: 30, email: \"alice@example.com\", address: \"123 Main St\"}");
539 let formatted = format_expr(&expr, Some(40));
540 assert!(formatted.contains("\n"));
541 assert!(formatted.contains("{\n"));
542 }
543
544 #[test]
545 fn test_format_conditional() {
546 let expr = parse_test_expr("if very_long_condition_variable > 100 then \"yes\" else \"no\"");
547 let formatted = format_expr(&expr, Some(30));
548 assert!(formatted.contains("\n"));
549 }
550
551 #[test]
552 fn test_format_binary_op() {
553 let expr = parse_test_expr("very_long_variable_name + another_very_long_variable_name");
554 let formatted = format_expr(&expr, Some(30));
555 assert!(formatted.contains("\n"));
556 }
557
558 #[test]
559 fn test_format_lambda() {
560 let expr = parse_test_expr("(x, y) => x + y");
561 let formatted = format_expr(&expr, Some(80));
562 assert_eq!(formatted, "(x, y) => x + y");
563 }
564
565 #[test]
566 fn test_format_nested_list() {
567 let expr = parse_test_expr("[[1, 2, 3], [4, 5, 6], [7, 8, 9]]");
568 let formatted = format_expr(&expr, Some(20));
569 assert!(formatted.contains("\n"));
570 }
571
572 #[test]
573 fn test_format_function_call() {
574 let expr = parse_test_expr("map([1, 2, 3], x => x * 2)");
575 let formatted = format_expr(&expr, Some(80));
576 assert_eq!(formatted, "map([1, 2, 3], x => x * 2)");
577 }
578
579 #[test]
580 fn test_format_do_block() {
581 let expr = parse_test_expr("do { x = 1\n return x }");
582 let formatted = format_expr(&expr, Some(80));
583 assert!(formatted.contains("do {"));
584 assert!(formatted.contains("return"));
585 }
586
587 #[test]
588 fn test_multiple_statements() {
589 use crate::parser::Rule;
590
591 let source = "x = [1, 2, 3, 4, 5]\ny = {name: \"Alice\", age: 30}\nz = x + y";
592 let pairs = get_pairs(source).unwrap();
593
594 let mut formatted_statements = Vec::new();
595
596 for pair in pairs {
597 if pair.as_rule() == Rule::statement {
598 if let Some(inner_pair) = pair.into_inner().next() {
599 if let Ok(expr) = pairs_to_expr(inner_pair.into_inner()) {
600 let formatted = format_expr(&expr, Some(80));
601 formatted_statements.push(formatted);
602 }
603 }
604 }
605 }
606
607 assert_eq!(formatted_statements.len(), 3);
609 assert_eq!(formatted_statements[0], "x = [1, 2, 3, 4, 5]");
610 assert_eq!(formatted_statements[1], "y = {name: \"Alice\", age: 30}");
611 assert_eq!(formatted_statements[2], "z = x + y");
612 }
613
614 #[test]
615 fn test_comments_are_preserved() {
616 use crate::parser::Rule;
617
618 let source = "// Comment 1\nx = [1, 2, 3]\n// Comment 2\ny = x + 1";
619 let pairs = get_pairs(source).unwrap();
620
621 let mut formatted_statements = Vec::new();
622
623 for pair in pairs {
624 if pair.as_rule() == Rule::statement {
625 if let Some(inner_pair) = pair.into_inner().next() {
626 match inner_pair.as_rule() {
627 Rule::comment => {
628 formatted_statements.push(inner_pair.as_str().to_string());
630 }
631 _ => {
632 if let Ok(expr) = pairs_to_expr(inner_pair.into_inner()) {
634 let formatted = format_expr(&expr, Some(80));
635 formatted_statements.push(formatted);
636 }
637 }
638 }
639 }
640 }
641 }
642
643 assert_eq!(formatted_statements.len(), 4);
645 assert_eq!(formatted_statements[0], "// Comment 1");
646 assert_eq!(formatted_statements[1], "x = [1, 2, 3]");
647 assert_eq!(formatted_statements[2], "// Comment 2");
648 assert_eq!(formatted_statements[3], "y = x + 1");
649 }
650
651 #[test]
652 fn test_comments_with_formatted_code() {
653 use crate::parser::Rule;
654
655 let source = "// Configuration\nconfig = {name: \"test\", debug: true}\n// Process data\nresult = [1, 2, 3]";
656 let pairs = get_pairs(source).unwrap();
657
658 let mut formatted_statements = Vec::new();
659
660 for pair in pairs {
661 if pair.as_rule() == Rule::statement {
662 if let Some(inner_pair) = pair.into_inner().next() {
663 match inner_pair.as_rule() {
664 Rule::comment => {
665 formatted_statements.push(inner_pair.as_str().to_string());
666 }
667 _ => {
668 if let Ok(expr) = pairs_to_expr(inner_pair.into_inner()) {
669 let formatted = format_expr(&expr, Some(80));
670 formatted_statements.push(formatted);
671 }
672 }
673 }
674 }
675 }
676 }
677
678 let result = formatted_statements.join("\n");
679
680 assert_eq!(formatted_statements.len(), 4);
682 assert_eq!(formatted_statements[0], "// Configuration");
683 assert!(formatted_statements[1].starts_with("config = "));
684 assert_eq!(formatted_statements[2], "// Process data");
685 assert_eq!(formatted_statements[3], "result = [1, 2, 3]");
686
687 assert!(result.contains("// Configuration"));
689 assert!(result.contains("// Process data"));
690 }
691
692 #[test]
693 fn test_output_declaration_with_assignment() {
694 use crate::parser::Rule;
695
696 let source = "output result = 42";
697 let pairs = get_pairs(source).unwrap();
698
699 let mut formatted_statements = Vec::new();
700
701 for pair in pairs {
702 if pair.as_rule() == Rule::statement {
703 if let Some(inner_pair) = pair.into_inner().next() {
704 match inner_pair.as_rule() {
705 Rule::output_declaration => {
706 let mut inner = inner_pair.into_inner();
708 let assignment_or_ident = inner.next().unwrap();
709
710 let formatted = if assignment_or_ident.as_rule() == Rule::assignment {
711 let mut assignment_inner = assignment_or_ident.into_inner();
713 let ident = assignment_inner.next().unwrap().as_str();
714 let value_expr = pairs_to_expr(assignment_inner.next().unwrap().into_inner()).unwrap();
715 let value_formatted = format_expr(&value_expr, Some(80));
716 format!("{} = {}", ident, value_formatted)
717 } else {
718 assignment_or_ident.as_str().to_string()
719 };
720
721 formatted_statements.push(format!("output {}", formatted));
722 }
723 _ => {
724 if let Ok(expr) = pairs_to_expr(inner_pair.into_inner()) {
725 let formatted = format_expr(&expr, Some(80));
726 formatted_statements.push(formatted);
727 }
728 }
729 }
730 }
731 }
732 }
733
734 assert_eq!(formatted_statements.len(), 1);
735 assert_eq!(formatted_statements[0], "output result = 42");
736 }
737
738 #[test]
739 fn test_output_statement_separate() {
740 use crate::parser::Rule;
741
742 let source = "result = 42\noutput result";
743 let pairs = get_pairs(source).unwrap();
744
745 let mut formatted_statements = Vec::new();
746
747 for pair in pairs {
748 if pair.as_rule() == Rule::statement {
749 if let Some(inner_pair) = pair.into_inner().next() {
750 match inner_pair.as_rule() {
751 Rule::output_declaration => {
752 let mut inner = inner_pair.into_inner();
754 let assignment_or_ident = inner.next().unwrap();
755
756 let formatted = if assignment_or_ident.as_rule() == Rule::assignment {
757 let mut assignment_inner = assignment_or_ident.into_inner();
759 let ident = assignment_inner.next().unwrap().as_str();
760 let value_expr = pairs_to_expr(assignment_inner.next().unwrap().into_inner()).unwrap();
761 let value_formatted = format_expr(&value_expr, Some(80));
762 format!("{} = {}", ident, value_formatted)
763 } else {
764 assignment_or_ident.as_str().to_string()
765 };
766
767 formatted_statements.push(format!("output {}", formatted));
768 }
769 _ => {
770 if let Ok(expr) = pairs_to_expr(inner_pair.into_inner()) {
771 let formatted = format_expr(&expr, Some(80));
772 formatted_statements.push(formatted);
773 }
774 }
775 }
776 }
777 }
778 }
779
780 assert_eq!(formatted_statements.len(), 2);
781 assert_eq!(formatted_statements[0], "result = 42");
782 assert_eq!(formatted_statements[1], "output result");
783 }
784
785 #[test]
786 fn test_output_with_complex_expression() {
787 use crate::parser::Rule;
788
789 let source = "output total = [1, 2, 3] into sum";
790 let pairs = get_pairs(source).unwrap();
791
792 let mut formatted_statements = Vec::new();
793
794 for pair in pairs {
795 if pair.as_rule() == Rule::statement {
796 if let Some(inner_pair) = pair.into_inner().next() {
797 match inner_pair.as_rule() {
798 Rule::output_declaration => {
799 let mut inner = inner_pair.into_inner();
801 let assignment_or_ident = inner.next().unwrap();
802
803 let formatted = if assignment_or_ident.as_rule() == Rule::assignment {
804 let mut assignment_inner = assignment_or_ident.into_inner();
806 let ident = assignment_inner.next().unwrap().as_str();
807 let value_expr = pairs_to_expr(assignment_inner.next().unwrap().into_inner()).unwrap();
808 let value_formatted = format_expr(&value_expr, Some(80));
809 format!("{} = {}", ident, value_formatted)
810 } else {
811 assignment_or_ident.as_str().to_string()
812 };
813
814 formatted_statements.push(format!("output {}", formatted));
815 }
816 _ => {
817 if let Ok(expr) = pairs_to_expr(inner_pair.into_inner()) {
818 let formatted = format_expr(&expr, Some(80));
819 formatted_statements.push(formatted);
820 }
821 }
822 }
823 }
824 }
825 }
826
827 assert_eq!(formatted_statements.len(), 1);
828 assert_eq!(formatted_statements[0], "output total = [1, 2, 3] into sum");
829 }
830
831 #[test]
832 fn test_end_of_line_comments() {
833 use crate::parser::Rule;
834
835 let source = "x = 5 // this is an end-of-line comment\ny = 10";
836 let pairs = get_pairs(source).unwrap();
837
838 let mut formatted_statements = Vec::new();
839
840 for pair in pairs {
841 if pair.as_rule() == Rule::statement {
842 let mut inner_pairs = pair.into_inner();
843
844 if let Some(first_pair) = inner_pairs.next() {
845 let formatted = match first_pair.as_rule() {
846 Rule::comment => first_pair.as_str().to_string(),
847 _ => {
848 if let Ok(expr) = pairs_to_expr(first_pair.into_inner()) {
849 format_expr(&expr, Some(80))
850 } else {
851 continue;
852 }
853 }
854 };
855
856 if let Some(eol_comment) = inner_pairs.next() {
858 if eol_comment.as_rule() == Rule::comment {
859 formatted_statements.push(format!("{} {}", formatted, eol_comment.as_str()));
860 } else {
861 formatted_statements.push(formatted);
862 }
863 } else {
864 formatted_statements.push(formatted);
865 }
866 }
867 }
868 }
869
870 assert_eq!(formatted_statements.len(), 2);
871 assert_eq!(formatted_statements[0], "x = 5 // this is an end-of-line comment");
872 assert_eq!(formatted_statements[1], "y = 10");
873 }
874
875 #[test]
876 fn test_multiple_end_of_line_comments() {
877 use crate::parser::Rule;
878
879 let source = "a = 1 // comment 1\nb = 2 // comment 2\nc = 3";
880 let pairs = get_pairs(source).unwrap();
881
882 let mut formatted_statements = Vec::new();
883
884 for pair in pairs {
885 if pair.as_rule() == Rule::statement {
886 let mut inner_pairs = pair.into_inner();
887
888 if let Some(first_pair) = inner_pairs.next() {
889 let formatted = match first_pair.as_rule() {
890 Rule::comment => first_pair.as_str().to_string(),
891 _ => {
892 if let Ok(expr) = pairs_to_expr(first_pair.into_inner()) {
893 format_expr(&expr, Some(80))
894 } else {
895 continue;
896 }
897 }
898 };
899
900 if let Some(eol_comment) = inner_pairs.next() {
902 if eol_comment.as_rule() == Rule::comment {
903 formatted_statements.push(format!("{} {}", formatted, eol_comment.as_str()));
904 } else {
905 formatted_statements.push(formatted);
906 }
907 } else {
908 formatted_statements.push(formatted);
909 }
910 }
911 }
912 }
913
914 assert_eq!(formatted_statements.len(), 3);
915 assert_eq!(formatted_statements[0], "a = 1 // comment 1");
916 assert_eq!(formatted_statements[1], "b = 2 // comment 2");
917 assert_eq!(formatted_statements[2], "c = 3");
918 }
919
920 #[test]
921 fn test_eol_comments_not_joined_with_next_line() {
922 use crate::parser::Rule;
923
924 let source = "x = 1 // first value\ny = 2 // second value\nz = x + y";
926 let pairs = get_pairs(source).unwrap();
927
928 let mut formatted_statements = Vec::new();
929
930 for pair in pairs {
931 if pair.as_rule() == Rule::statement {
932 let mut inner_pairs = pair.into_inner();
933
934 if let Some(first_pair) = inner_pairs.next() {
935 let formatted = match first_pair.as_rule() {
936 Rule::comment => first_pair.as_str().to_string(),
937 _ => {
938 if let Ok(expr) = pairs_to_expr(first_pair.into_inner()) {
939 format_expr(&expr, Some(80))
940 } else {
941 continue;
942 }
943 }
944 };
945
946 if let Some(eol_comment) = inner_pairs.next() {
948 if eol_comment.as_rule() == Rule::comment {
949 formatted_statements.push(format!("{} {}", formatted, eol_comment.as_str()));
950 } else {
951 formatted_statements.push(formatted);
952 }
953 } else {
954 formatted_statements.push(formatted);
955 }
956 }
957 }
958 }
959
960 let result = formatted_statements.join("\n");
962
963 assert_eq!(formatted_statements.len(), 3);
964 assert_eq!(result.lines().count(), 3);
966 assert_eq!(formatted_statements[0], "x = 1 // first value");
967 assert_eq!(formatted_statements[1], "y = 2 // second value");
968 assert_eq!(formatted_statements[2], "z = x + y");
969
970 for line in result.lines() {
972 assert_eq!(line.matches('=').count(), 1, "Line should not contain multiple statements: {}", line);
974 }
975 }
976
977 #[test]
978 fn test_eol_comments_with_line_breaking() {
979 use crate::parser::Rule;
980
981 let source = "longRecord = {name: \"Alice\", age: 30, email: \"alice@example.com\", address: \"123 Main St\"} // user data";
983 let pairs = get_pairs(source).unwrap();
984
985 let mut formatted_statements = Vec::new();
986
987 for pair in pairs {
988 if pair.as_rule() == Rule::statement {
989 let mut inner_pairs = pair.into_inner();
990
991 if let Some(first_pair) = inner_pairs.next() {
992 let formatted = match first_pair.as_rule() {
993 Rule::comment => first_pair.as_str().to_string(),
994 _ => {
995 if let Ok(expr) = pairs_to_expr(first_pair.into_inner()) {
996 format_expr(&expr, Some(40))
998 } else {
999 continue;
1000 }
1001 }
1002 };
1003
1004 if let Some(eol_comment) = inner_pairs.next() {
1006 if eol_comment.as_rule() == Rule::comment {
1007 formatted_statements.push(format!("{} {}", formatted, eol_comment.as_str()));
1008 } else {
1009 formatted_statements.push(formatted);
1010 }
1011 } else {
1012 formatted_statements.push(formatted);
1013 }
1014 }
1015 }
1016 }
1017
1018 assert_eq!(formatted_statements.len(), 1);
1019 let result = &formatted_statements[0];
1020
1021 assert!(result.ends_with("// user data"));
1023 assert!(result.contains("longRecord = "));
1025 }
1026
1027 #[test]
1028 fn test_actual_line_breaking_behavior() {
1029 use crate::parser::Rule;
1030
1031 let source = "x = {name: \"Alice\", age: 30, email: \"alice@example.com\", address: \"123 Main St\", city: \"Springfield\"}";
1033 let pairs = get_pairs(source).unwrap();
1034
1035 for pair in pairs {
1036 if pair.as_rule() == Rule::statement {
1037 if let Some(inner_pair) = pair.into_inner().next() {
1038 if let Ok(expr) = pairs_to_expr(inner_pair.into_inner()) {
1039 let formatted = format_expr(&expr, Some(40));
1040 println!("Formatted output:\n{}", formatted);
1041 println!("Line count: {}", formatted.lines().count());
1042
1043 if formatted.lines().count() > 1 {
1045 println!("✓ Lines were broken");
1046 } else {
1047 println!("✗ No line breaking occurred - output is {} chars", formatted.len());
1048 }
1049 }
1050 }
1051 }
1052 }
1053 }
1054
1055 #[test]
1056 fn test_multiline_input_gets_collapsed() {
1057 use crate::parser::Rule;
1058
1059 let source = "x = [\n 1,\n 2,\n 3\n]";
1061 let pairs = get_pairs(source).unwrap();
1062
1063 for pair in pairs {
1064 if pair.as_rule() == Rule::statement {
1065 if let Some(inner_pair) = pair.into_inner().next() {
1066 if let Ok(expr) = pairs_to_expr(inner_pair.into_inner()) {
1067 let formatted = format_expr(&expr, Some(80));
1068 println!("Input:\n{}", source);
1069 println!("Output:\n{}", formatted);
1070
1071 assert_eq!(formatted, "x = [1, 2, 3]");
1073 }
1074 }
1075 }
1076 }
1077 }
1078
1079 #[test]
1080 fn test_empty_lines_between_statements() {
1081 use crate::parser::Rule;
1082
1083 let source = "x = 1\n\ny = 2\n\n\n\nz = 3";
1085 let pairs = get_pairs(source).unwrap();
1086
1087 let mut statements_with_positions = Vec::new();
1088
1089 for pair in pairs {
1090 if pair.as_rule() == Rule::statement {
1091 let start_line = pair.as_span().start_pos().line_col().0;
1092 let end_line = pair.as_span().end_pos().line_col().0;
1093
1094 if let Some(inner_pair) = pair.into_inner().next() {
1095 if let Ok(expr) = pairs_to_expr(inner_pair.into_inner()) {
1096 let formatted = format_expr(&expr, Some(80));
1097 statements_with_positions.push((formatted, start_line, end_line));
1098 }
1099 }
1100 }
1101 }
1102
1103 println!("Statement positions: {:?}", statements_with_positions);
1104
1105 let result = join_statements_with_spacing(&statements_with_positions);
1107 println!("Output with preserved spacing:\n{}", result);
1108
1109 assert_eq!(result.lines().count(), 6); assert_eq!(result, "x = 1\n\ny = 2\n\n\nz = 3");
1120 }
1121
1122 #[test]
1123 fn test_assignment_with_long_list_indentation() {
1124 use crate::parser::Rule;
1125
1126 let source = "ingredients = [{name: \"sugar\", amount: 1}, {name: \"flour\", amount: 2}]";
1128 let pairs = get_pairs(source).unwrap();
1129
1130 for pair in pairs {
1131 if pair.as_rule() == Rule::statement {
1132 if let Some(inner_pair) = pair.into_inner().next() {
1133 if let Ok(expr) = pairs_to_expr(inner_pair.into_inner()) {
1134 let formatted = format_expr(&expr, Some(40));
1135 println!("Formatted:\n{}", formatted);
1136
1137 assert!(formatted.contains("[\n {"));
1139 assert!(!formatted.contains(" ")); }
1141 }
1142 }
1143 }
1144 }
1145}