1use crate::brace_expansion;
2use crate::executor;
3use crate::lexer;
4use crate::parser;
5use crate::state;
6use std::collections::HashSet;
7use std::sync::atomic::{AtomicBool, Ordering};
8
9pub fn line_contains_heredoc(line: &str, shell_state: &state::ShellState) -> Option<String> {
12 match lexer::lex(line, shell_state) {
14 Ok(tokens) => {
15 for token in tokens {
17 if let lexer::Token::RedirHereDoc(delimiter, _quoted) = token {
18 return Some(delimiter);
19 }
20 }
21 None
22 }
23 Err(_) => None,
24 }
25}
26
27pub fn contains_keyword(line: &str, keyword: &str) -> bool {
30 let mut chars = line.chars().peekable();
31 let mut in_single_quote = false;
32 let mut in_double_quote = false;
33 let mut escaped = false;
34 let mut current_word = String::new();
35
36 while let Some(ch) = chars.next() {
37 if escaped {
38 escaped = false;
39 current_word.push(ch);
41 continue;
42 }
43
44 if in_single_quote {
45 if ch == '\'' {
46 in_single_quote = false;
47 } else {
48 current_word.push(ch);
49 }
50 continue;
51 }
52
53 if in_double_quote {
54 if ch == '"' {
55 in_double_quote = false;
56 } else if ch == '\\' {
57 escaped = true;
58 } else {
59 current_word.push(ch);
60 }
61 continue;
62 }
63
64 match ch {
65 '#' => {
66 if current_word.is_empty() {
67 return false; }
69 current_word.push(ch); }
71 '\'' => {
72 in_single_quote = true;
73 current_word.push(ch);
74 }
75 '"' => {
76 in_double_quote = true;
77 current_word.push(ch);
78 }
79 '\\' => escaped = true,
80 ' ' | '\t' | '\n' | ';' | '|' | '&' | '(' | ')' | '{' | '}' => {
81 if current_word == keyword {
82 return true;
83 }
84 current_word.clear();
85 }
86 _ => current_word.push(ch),
87 }
88 }
89
90 current_word == keyword
92}
93
94pub fn starts_with_keyword(line: &str, keyword: &str) -> bool {
96 let mut chars = line.chars().peekable();
97 let mut current_word = String::new();
98
99 while let Some(&ch) = chars.peek() {
101 if ch == ' ' || ch == '\t' {
102 chars.next();
103 } else {
104 break;
105 }
106 }
107
108 while let Some(ch) = chars.next() {
109 match ch {
110 ' ' | '\t' | '\n' | ';' | '|' | '&' | '(' | ')' | '{' | '}' => {
111 return current_word == keyword;
112 }
113 _ => current_word.push(ch),
114 }
115 }
116
117 current_word == keyword
118}
119
120pub fn execute_line(line: &str, shell_state: &mut state::ShellState) {
121 match lexer::lex(line, shell_state) {
122 Ok(tokens) => match lexer::expand_aliases(tokens, shell_state, &mut HashSet::new()) {
123 Ok(expanded_tokens) => match brace_expansion::expand_braces(expanded_tokens) {
124 Ok(brace_expanded_tokens) => match parser::parse(brace_expanded_tokens) {
125 Ok(ast) => {
126 let exit_code = executor::execute(ast, shell_state);
127 shell_state.set_last_exit_code(exit_code);
128 }
129 Err(e) => {
130 if shell_state.colors_enabled {
131 eprintln!(
132 "{}Parse error: {}\x1b[0m",
133 shell_state.color_scheme.error, e
134 );
135 } else {
136 eprintln!("Parse error: {}", e);
137 }
138 shell_state.set_last_exit_code(1);
139 }
140 },
141 Err(e) => {
142 if shell_state.colors_enabled {
143 eprintln!(
144 "{}Brace expansion error: {}\x1b[0m",
145 shell_state.color_scheme.error, e
146 );
147 } else {
148 eprintln!("Brace expansion error: {}", e);
149 }
150 shell_state.set_last_exit_code(1);
151 }
152 },
153 Err(e) => {
154 if shell_state.colors_enabled {
155 eprintln!(
156 "{}Alias expansion error: {}\x1b[0m",
157 shell_state.color_scheme.error, e
158 );
159 } else {
160 eprintln!("Alias expansion error: {}", e);
161 }
162 shell_state.set_last_exit_code(1);
163 }
164 },
165 Err(e) => {
166 if shell_state.colors_enabled {
167 eprintln!("{}Lex error: {}\x1b[0m", shell_state.color_scheme.error, e);
168 } else {
169 eprintln!("Lex error: {}", e);
170 }
171 shell_state.set_last_exit_code(1);
172 }
173 }
174}
175
176pub fn execute_script(
177 content: &str,
178 shell_state: &mut state::ShellState,
179 shutdown_flag: Option<&AtomicBool>,
180) {
181 let mut current_block = String::new();
182 let mut in_if_block = false;
183 let mut if_depth = 0;
184 let mut in_case_block = false;
185 let mut in_function_block = false;
186 let mut in_group_block = false;
187 let mut brace_depth = 0;
188 let mut in_for_block = false;
189 let mut for_depth = 0;
190 let mut in_while_block = false;
191 let mut while_depth = 0;
192
193 let mut in_double_quote = false;
195 let mut in_single_quote = false;
196
197 let lines: Vec<&str> = content.lines().collect();
198 let mut i = 0;
199
200 while i < lines.len() {
201 let line = lines[i];
202 state::process_pending_signals(shell_state);
204
205 if let Some(flag) = shutdown_flag {
207 if flag.load(Ordering::Relaxed) {
208 eprintln!("Script interrupted by SIGTERM");
209 break;
210 }
211 }
212
213 if shell_state.exit_requested {
215 break;
216 }
217
218 if line.starts_with("#!") {
220 i += 1;
221 continue;
222 }
223
224 let mut chars = line.chars().peekable();
226 let mut escaped = false;
227
228 while let Some(ch) = chars.next() {
229 if escaped {
230 escaped = false;
231 continue;
232 }
233
234 if in_single_quote {
235 if ch == '\'' {
236 in_single_quote = false;
237 }
238 continue;
239 }
240
241 if in_double_quote {
242 if ch == '"' {
243 in_double_quote = false;
244 } else if ch == '\\' {
245 escaped = true;
246 }
247 continue;
248 }
249
250 match ch {
251 '#' => break, '\'' => in_single_quote = true,
253 '"' => in_double_quote = true,
254 '\\' => escaped = true,
255 _ => {}
256 }
257 }
258
259 let trimmed = line.trim();
260 if !in_double_quote && !in_single_quote && (trimmed.is_empty() || trimmed.starts_with("#"))
261 {
262 i += 1;
263 continue;
264 }
265
266 let keywords_active = !in_double_quote && !in_single_quote;
267
268 if keywords_active && !in_function_block {
269 if starts_with_keyword(line, "if") {
270 in_if_block = true;
271 if_depth += 1;
272 } else if starts_with_keyword(line, "case") {
273 in_case_block = true;
274 } else if starts_with_keyword(line, "for") {
275 in_for_block = true;
276 for_depth += 1;
277 } else if starts_with_keyword(line, "while") {
278 in_while_block = true;
279 while_depth += 1;
280 } else if {
281 let trimmed = line.trim();
282 trimmed == "{" || trimmed.starts_with("{ ") || trimmed.starts_with("{\t")
283 } {
284 in_group_block = true;
285 brace_depth += line.matches('{').count() as i32;
286 brace_depth -= line.matches('}').count() as i32;
287 }
288 }
289
290 if keywords_active
291 && (line.contains("() {") || (trimmed.ends_with("()") && !in_function_block))
292 {
293 in_function_block = true;
294 brace_depth += line.matches('{').count() as i32;
295 brace_depth -= line.matches('}').count() as i32;
296 } else if in_function_block || in_group_block {
297 brace_depth += line.matches('{').count() as i32;
298 brace_depth -= line.matches('}').count() as i32;
299 }
300
301 if !current_block.is_empty() {
302 current_block.push('\n');
303 }
304 current_block.push_str(line);
305
306 if keywords_active {
307 if (in_function_block || in_group_block) && brace_depth == 0 {
308 in_function_block = false;
309 in_group_block = false;
310 execute_line(¤t_block, shell_state);
311 current_block.clear();
312
313 if shell_state.exit_requested {
314 break;
315 }
316 } else if in_if_block && contains_keyword(line, "fi") {
317 if_depth -= 1;
318 if if_depth == 0 {
319 in_if_block = false;
320 execute_line(¤t_block, shell_state);
321 current_block.clear();
322
323 if shell_state.exit_requested {
324 break;
325 }
326 }
327 } else if in_for_block && contains_keyword(line, "done") {
328 for_depth -= 1;
329 if for_depth == 0 {
330 in_for_block = false;
331 execute_line(¤t_block, shell_state);
332 current_block.clear();
333
334 if shell_state.exit_requested {
335 break;
336 }
337 }
338 } else if in_while_block && contains_keyword(line, "done") {
339 while_depth -= 1;
340 if while_depth == 0 {
341 in_while_block = false;
342 execute_line(¤t_block, shell_state);
343 current_block.clear();
344
345 if shell_state.exit_requested {
346 break;
347 }
348 }
349 } else if in_case_block && contains_keyword(line, "esac") {
350 in_case_block = false;
351 execute_line(¤t_block, shell_state);
352 current_block.clear();
353
354 if shell_state.exit_requested {
355 break;
356 }
357 } else if !in_if_block
358 && !in_case_block
359 && !in_function_block
360 && !in_group_block
361 && !in_for_block
362 && !in_while_block
363 {
364 if let Some(delimiter) = line_contains_heredoc(¤t_block, shell_state) {
365 i += 1;
366 let mut heredoc_content = String::new();
367 while i < lines.len() {
368 let content_line = lines[i];
369 if content_line.trim() == delimiter.trim() {
370 break;
371 }
372 if !heredoc_content.is_empty() {
373 heredoc_content.push('\n');
374 }
375 heredoc_content.push_str(content_line);
376 i += 1;
377 }
378 shell_state.pending_heredoc_content = Some(heredoc_content);
379 execute_line(¤t_block, shell_state);
380 current_block.clear();
381 } else if !in_single_quote
382 && !in_double_quote
383 && (line.ends_with(';') || !line.trim_end().ends_with('\\'))
384 {
385 execute_line(¤t_block, shell_state);
386 current_block.clear();
387 }
388 }
389 }
390 i += 1;
391 }
392}