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 let mut in_until_block = false;
193 let mut until_depth = 0;
194
195 let mut in_double_quote = false;
197 let mut in_single_quote = false;
198
199 let lines: Vec<&str> = content.lines().collect();
200 let mut i = 0;
201
202 while i < lines.len() {
203 let line = lines[i];
204 state::process_pending_signals(shell_state);
206
207 if let Some(flag) = shutdown_flag {
209 if flag.load(Ordering::Relaxed) {
210 eprintln!("Script interrupted by SIGTERM");
211 break;
212 }
213 }
214
215 if shell_state.exit_requested {
217 break;
218 }
219
220 if line.starts_with("#!") {
222 i += 1;
223 continue;
224 }
225
226 let mut chars = line.chars().peekable();
228 let mut escaped = false;
229
230 while let Some(ch) = chars.next() {
231 if escaped {
232 escaped = false;
233 continue;
234 }
235
236 if in_single_quote {
237 if ch == '\'' {
238 in_single_quote = false;
239 }
240 continue;
241 }
242
243 if in_double_quote {
244 if ch == '"' {
245 in_double_quote = false;
246 } else if ch == '\\' {
247 escaped = true;
248 }
249 continue;
250 }
251
252 match ch {
253 '#' => break, '\'' => in_single_quote = true,
255 '"' => in_double_quote = true,
256 '\\' => escaped = true,
257 _ => {}
258 }
259 }
260
261 let trimmed = line.trim();
262 if !in_double_quote && !in_single_quote && (trimmed.is_empty() || trimmed.starts_with("#"))
263 {
264 i += 1;
265 continue;
266 }
267
268 let keywords_active = !in_double_quote && !in_single_quote;
269
270 if keywords_active && !in_function_block {
271 if starts_with_keyword(line, "if") {
272 in_if_block = true;
273 if_depth += 1;
274 } else if starts_with_keyword(line, "case") {
275 in_case_block = true;
276 } else if starts_with_keyword(line, "for") {
277 in_for_block = true;
278 for_depth += 1;
279 } else if starts_with_keyword(line, "while") {
280 in_while_block = true;
281 while_depth += 1;
282 } else if starts_with_keyword(line, "until") {
283 in_until_block = true;
284 until_depth += 1;
285 } else if {
286 let trimmed = line.trim();
287 trimmed == "{" || trimmed.starts_with("{ ") || trimmed.starts_with("{\t")
288 } {
289 in_group_block = true;
290 brace_depth += line.matches('{').count() as i32;
291 brace_depth -= line.matches('}').count() as i32;
292 }
293 }
294
295 if keywords_active
296 && (line.contains("() {") || (trimmed.ends_with("()") && !in_function_block))
297 {
298 in_function_block = true;
299 brace_depth += line.matches('{').count() as i32;
300 brace_depth -= line.matches('}').count() as i32;
301 } else if in_function_block || in_group_block {
302 brace_depth += line.matches('{').count() as i32;
303 brace_depth -= line.matches('}').count() as i32;
304 }
305
306 if !current_block.is_empty() {
307 current_block.push('\n');
308 }
309 current_block.push_str(line);
310
311 if keywords_active {
312 if (in_function_block || in_group_block) && brace_depth == 0 {
313 in_function_block = false;
314 in_group_block = false;
315 execute_line(¤t_block, shell_state);
316 current_block.clear();
317
318 if shell_state.exit_requested {
319 break;
320 }
321 } else if in_if_block && contains_keyword(line, "fi") {
322 if_depth -= 1;
323 if if_depth == 0 {
324 in_if_block = false;
325 if !in_for_block && !in_while_block && !in_until_block && !in_function_block && !in_group_block && !in_case_block {
327 execute_line(¤t_block, shell_state);
328 current_block.clear();
329
330 if shell_state.exit_requested {
331 break;
332 }
333 }
334 }
335 } else if in_for_block && contains_keyword(line, "done") {
336 for_depth -= 1;
337 if for_depth == 0 {
338 in_for_block = false;
339 execute_line(¤t_block, shell_state);
340 current_block.clear();
341
342 if shell_state.exit_requested {
343 break;
344 }
345 }
346 } else if in_while_block && contains_keyword(line, "done") {
347 while_depth -= 1;
348 if while_depth == 0 {
349 in_while_block = false;
350 execute_line(¤t_block, shell_state);
351 current_block.clear();
352
353 if shell_state.exit_requested {
354 break;
355 }
356 }
357 } else if in_until_block && contains_keyword(line, "done") {
358 until_depth -= 1;
359 if until_depth == 0 {
360 in_until_block = false;
361 execute_line(¤t_block, shell_state);
362 current_block.clear();
363
364 if shell_state.exit_requested {
365 break;
366 }
367 }
368 } else if in_case_block && contains_keyword(line, "esac") {
369 in_case_block = false;
370 execute_line(¤t_block, shell_state);
371 current_block.clear();
372
373 if shell_state.exit_requested {
374 break;
375 }
376 } else if !in_if_block
377 && !in_case_block
378 && !in_function_block
379 && !in_group_block
380 && !in_for_block
381 && !in_while_block
382 && !in_until_block
383 {
384 if let Some(delimiter) = line_contains_heredoc(¤t_block, shell_state) {
385 i += 1;
386 let mut heredoc_content = String::new();
387 while i < lines.len() {
388 let content_line = lines[i];
389 if content_line.trim() == delimiter.trim() {
390 break;
391 }
392 if !heredoc_content.is_empty() {
393 heredoc_content.push('\n');
394 }
395 heredoc_content.push_str(content_line);
396 i += 1;
397 }
398 shell_state.pending_heredoc_content = Some(heredoc_content);
399 execute_line(¤t_block, shell_state);
400 current_block.clear();
401 } else if !in_single_quote
402 && !in_double_quote
403 && (line.ends_with(';') || !line.trim_end().ends_with('\\'))
404 {
405 execute_line(¤t_block, shell_state);
406 current_block.clear();
407 }
408 }
409 }
410 i += 1;
411 }
412}