1use regex::Regex;
2use std::fs::File;
3use std::io::{self, BufRead, BufReader, Write};
4use std::path::Path;
5
6#[derive(Debug, PartialEq, Clone)]
7enum ConstructType {
8 Proc,
9 Export,
10 ExportLine,
11 Begin,
12 End,
13 While,
14 Repeat,
15 If,
16 Else,
17}
18
19impl ConstructType {
20 fn from_str(s: &str) -> Option<Self> {
21 match s {
22 "proc" => Some(Self::Proc),
23 "export" => Some(Self::Export),
24 "exportLine" => Some(Self::ExportLine),
25 "begin" => Some(Self::Begin),
26 "end" => Some(Self::End),
27 "while" => Some(Self::While),
28 "repeat" => Some(Self::Repeat),
29 "if" => Some(Self::If),
30 "else" => Some(Self::Else),
31 _ => None,
32 }
33 }
34}
35
36const INDENT: &str = " ";
37
38fn is_comment(line: &str) -> bool {
39 line.trim_start().starts_with('#')
40}
41
42fn is_single_export_line(line: &str) -> bool {
43 let re = Regex::new(r"^export\.\w*(::\w+)+$").unwrap();
44 re.is_match(line)
45}
46
47pub fn format_code(code: &str) -> String {
48 let mut formatted_code = String::new();
49 let mut indentation_level = 0;
50 let mut construct_stack = Vec::new();
51 let mut last_line_was_empty = false;
52 let mut last_was_export_line = false;
53
54 let lines = code.lines().peekable();
55
56 for line in lines {
57 let trimmed_line = line.trim();
58 let first_word = trimmed_line.split('.').next();
59
60 if !trimmed_line.is_empty() {
61 if is_comment(trimmed_line) {
62 if last_was_export_line {
63 formatted_code.push_str(trimmed_line);
64 } else {
65 if let Some(prev_line) = formatted_code.lines().last() {
66 let prev_indent_level =
67 prev_line.chars().take_while(|&c| c == ' ').count() / 4;
68 if prev_line.trim_start().starts_with("export") {
69 formatted_code.push_str(&INDENT.repeat(prev_indent_level + 1));
70 } else {
71 formatted_code.push_str(&INDENT.repeat(indentation_level));
72 }
73 } else {
74 formatted_code.push_str(&INDENT.repeat(indentation_level));
75 }
76 formatted_code.push_str(trimmed_line);
77 }
78 formatted_code.push('\n');
79 last_line_was_empty = false;
80 continue;
81 }
82
83 if is_single_export_line(trimmed_line) {
84 formatted_code.push_str(trimmed_line);
85 formatted_code.push('\n');
86 last_line_was_empty = false;
87 last_was_export_line = true;
88 continue;
89 }
90
91 last_was_export_line = false;
92
93 if let Some(word) = first_word {
94 if let Some(construct) = ConstructType::from_str(word) {
95 match construct {
96 ConstructType::End => {
97 if let Some(last_construct) = construct_stack.pop() {
98 if last_construct != ConstructType::End && indentation_level > 0 {
99 indentation_level -= 1;
100 }
101 }
102 }
103 ConstructType::Else => {
104 if let Some(last_construct) = construct_stack.last() {
105 if *last_construct == ConstructType::If && indentation_level > 0 {
106 indentation_level -= 1;
107 }
108 }
109 }
110 _ => {
111 construct_stack.push(construct.clone());
112 }
113 }
114
115 formatted_code.push_str(&INDENT.repeat(indentation_level));
116 formatted_code.push_str(trimmed_line);
117 formatted_code.push('\n');
118 last_line_was_empty = false;
119
120 match construct {
121 ConstructType::Begin
122 | ConstructType::If
123 | ConstructType::Proc
124 | ConstructType::Export
125 | ConstructType::Repeat
126 | ConstructType::While
127 | ConstructType::Else => {
128 indentation_level += 1;
129 }
130 _ => {}
131 }
132
133 continue;
134 }
135 }
136
137 formatted_code.push_str(&INDENT.repeat(indentation_level));
138 formatted_code.push_str(trimmed_line);
139 formatted_code.push('\n');
140 last_line_was_empty = false;
141 } else if !last_line_was_empty {
142 formatted_code.push('\n');
143 last_line_was_empty = true;
144 }
145 }
146
147 formatted_code
148}
149
150pub fn format_file(file_path: &Path) -> io::Result<()> {
151 let file = File::open(file_path)?;
152 let mut input_code = String::new();
153
154 let reader = BufReader::new(file);
155 for line in reader.lines() {
156 input_code.push_str(&line?);
157 input_code.push('\n');
158 }
159
160 let formatted_code = format_code(&input_code);
161
162 let mut file = File::create(file_path)?;
163 file.write_all(formatted_code.as_bytes())?;
164
165 Ok(())
166}