1use std::cell::RefCell;
2use std::fs::{File, OpenOptions};
3use std::io::{BufRead, BufReader, Write, pipe};
4use std::os::fd::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
5use std::os::unix::process::CommandExt;
6use std::process::{Command, Stdio};
7use std::rc::Rc;
8
9use super::parser::{Ast, Redirection, ShellCommand};
10use super::state::ShellState;
11
12const MAX_SUBSHELL_DEPTH: usize = 100;
14
15fn execute_and_capture_output(ast: Ast, shell_state: &mut ShellState) -> Result<String, String> {
18 let (reader, writer) = pipe().map_err(|e| format!("Failed to create pipe: {}", e))?;
20
21 match &ast {
26 Ast::Pipeline(commands) => {
27 if commands.is_empty() {
29 return Ok(String::new());
30 }
31
32 if commands.len() == 1 {
33 let cmd = &commands[0];
35 if cmd.args.is_empty() {
36 return Ok(String::new());
37 }
38
39 let var_expanded_args = expand_variables_in_args(&cmd.args, shell_state);
41 let expanded_args = expand_wildcards(&var_expanded_args)
42 .map_err(|e| format!("Wildcard expansion failed: {}", e))?;
43
44 if expanded_args.is_empty() {
45 return Ok(String::new());
46 }
47
48 if shell_state.get_function(&expanded_args[0]).is_some() {
50 let previous_capture = shell_state.capture_output.clone();
52
53 let capture_buffer = Rc::new(RefCell::new(Vec::new()));
55 shell_state.capture_output = Some(capture_buffer.clone());
56
57 let function_call_ast = Ast::FunctionCall {
59 name: expanded_args[0].clone(),
60 args: expanded_args[1..].to_vec(),
61 };
62
63 let exit_code = execute(function_call_ast, shell_state);
64
65 let captured = capture_buffer.borrow().clone();
67 let output = String::from_utf8_lossy(&captured).trim_end().to_string();
68
69 shell_state.capture_output = previous_capture;
71
72 if exit_code == 0 {
73 Ok(output)
74 } else {
75 Err(format!("Function failed with exit code {}", exit_code))
76 }
77 } else if crate::builtins::is_builtin(&expanded_args[0]) {
78 let temp_cmd = ShellCommand {
79 args: expanded_args,
80 redirections: cmd.redirections.clone(),
81 compound: None,
82 };
83
84 let exit_code = crate::builtins::execute_builtin(
86 &temp_cmd,
87 shell_state,
88 Some(Box::new(writer)),
89 );
90
91 drop(temp_cmd); let mut output = String::new();
94 use std::io::Read;
95 let mut reader = reader;
96 reader
97 .read_to_string(&mut output)
98 .map_err(|e| format!("Failed to read output: {}", e))?;
99
100 if exit_code == 0 {
101 Ok(output.trim_end().to_string())
102 } else {
103 Err(format!("Command failed with exit code {}", exit_code))
104 }
105 } else {
106 drop(writer); let mut command = Command::new(&expanded_args[0]);
110 command.args(&expanded_args[1..]);
111 command.stdout(Stdio::piped());
112 command.stderr(Stdio::null()); let child_env = shell_state.get_env_for_child();
116 command.env_clear();
117 for (key, value) in child_env {
118 command.env(key, value);
119 }
120
121 let output = command
122 .output()
123 .map_err(|e| format!("Failed to execute command: {}", e))?;
124
125 if output.status.success() {
126 Ok(String::from_utf8_lossy(&output.stdout)
127 .trim_end()
128 .to_string())
129 } else {
130 Err(format!(
131 "Command failed with exit code {}",
132 output.status.code().unwrap_or(1)
133 ))
134 }
135 }
136 } else {
137 drop(writer); let previous_capture = shell_state.capture_output.clone();
142
143 let capture_buffer = Rc::new(RefCell::new(Vec::new()));
145 shell_state.capture_output = Some(capture_buffer.clone());
146
147 let exit_code = execute_pipeline(commands, shell_state);
149
150 let captured = capture_buffer.borrow().clone();
152 let output = String::from_utf8_lossy(&captured).trim_end().to_string();
153
154 shell_state.capture_output = previous_capture;
156
157 if exit_code == 0 {
158 Ok(output)
159 } else {
160 Err(format!("Pipeline failed with exit code {}", exit_code))
161 }
162 }
163 }
164 _ => {
165 drop(writer);
167
168 let previous_capture = shell_state.capture_output.clone();
170
171 let capture_buffer = Rc::new(RefCell::new(Vec::new()));
173 shell_state.capture_output = Some(capture_buffer.clone());
174
175 let exit_code = execute(ast, shell_state);
177
178 let captured = capture_buffer.borrow().clone();
180 let output = String::from_utf8_lossy(&captured).trim_end().to_string();
181
182 shell_state.capture_output = previous_capture;
184
185 if exit_code == 0 {
186 Ok(output)
187 } else {
188 Err(format!("Command failed with exit code {}", exit_code))
189 }
190 }
191 }
192}
193
194fn expand_variables_in_args(args: &[String], shell_state: &mut ShellState) -> Vec<String> {
195 let mut expanded_args = Vec::new();
196
197 for arg in args {
198 let expanded_arg = expand_variables_in_string(arg, shell_state);
200 expanded_args.push(expanded_arg);
201 }
202
203 expanded_args
204}
205
206pub fn expand_variables_in_string(input: &str, shell_state: &mut ShellState) -> String {
207 let mut result = String::new();
208 let mut chars = input.chars().peekable();
209
210 while let Some(ch) = chars.next() {
211 if ch == '$' {
212 if let Some(&'(') = chars.peek() {
214 chars.next(); if let Some(&'(') = chars.peek() {
218 chars.next(); let mut arithmetic_expr = String::new();
221 let mut paren_depth = 1;
222 let mut found_closing = false;
223
224 while let Some(c) = chars.next() {
225 if c == '(' {
226 paren_depth += 1;
227 arithmetic_expr.push(c);
228 } else if c == ')' {
229 paren_depth -= 1;
230 if paren_depth == 0 {
231 if let Some(&')') = chars.peek() {
233 chars.next(); found_closing = true;
235 break;
236 } else {
237 result.push_str("$((");
239 result.push_str(&arithmetic_expr);
240 result.push(')');
241 break;
242 }
243 }
244 arithmetic_expr.push(c);
245 } else {
246 arithmetic_expr.push(c);
247 }
248 }
249
250 if found_closing {
251 let mut expanded_expr = String::new();
255 let mut expr_chars = arithmetic_expr.chars().peekable();
256
257 while let Some(ch) = expr_chars.next() {
258 if ch == '$' {
259 let mut var_name = String::new();
261 if let Some(&c) = expr_chars.peek() {
262 if c == '?'
263 || c == '$'
264 || c == '0'
265 || c == '#'
266 || c == '*'
267 || c == '@'
268 || c.is_ascii_digit()
269 {
270 var_name.push(c);
271 expr_chars.next();
272 } else {
273 while let Some(&c) = expr_chars.peek() {
274 if c.is_alphanumeric() || c == '_' {
275 var_name.push(c);
276 expr_chars.next();
277 } else {
278 break;
279 }
280 }
281 }
282 }
283
284 if !var_name.is_empty() {
285 if let Some(value) = shell_state.get_var(&var_name) {
286 expanded_expr.push_str(&value);
287 } else {
288 expanded_expr.push('0');
290 }
291 } else {
292 expanded_expr.push('$');
293 }
294 } else {
295 expanded_expr.push(ch);
296 }
297 }
298
299 match crate::arithmetic::evaluate_arithmetic_expression(
300 &expanded_expr,
301 shell_state,
302 ) {
303 Ok(value) => {
304 result.push_str(&value.to_string());
305 }
306 Err(e) => {
307 if shell_state.colors_enabled {
309 result.push_str(&format!(
310 "{}arithmetic error: {}{}",
311 shell_state.color_scheme.error, e, "\x1b[0m"
312 ));
313 } else {
314 result.push_str(&format!("arithmetic error: {}", e));
315 }
316 }
317 }
318 } else {
319 result.push_str("$((");
321 result.push_str(&arithmetic_expr);
322 }
324 continue;
325 }
326
327 let mut sub_command = String::new();
329 let mut paren_depth = 1;
330
331 for c in chars.by_ref() {
332 if c == '(' {
333 paren_depth += 1;
334 sub_command.push(c);
335 } else if c == ')' {
336 paren_depth -= 1;
337 if paren_depth == 0 {
338 break;
339 }
340 sub_command.push(c);
341 } else {
342 sub_command.push(c);
343 }
344 }
345
346 if let Ok(tokens) = crate::lexer::lex(&sub_command, shell_state) {
349 let expanded_tokens = match crate::lexer::expand_aliases(
351 tokens,
352 shell_state,
353 &mut std::collections::HashSet::new(),
354 ) {
355 Ok(t) => t,
356 Err(_) => {
357 result.push_str("$(");
359 result.push_str(&sub_command);
360 result.push(')');
361 continue;
362 }
363 };
364
365 match crate::parser::parse(expanded_tokens) {
366 Ok(ast) => {
367 match execute_and_capture_output(ast, shell_state) {
369 Ok(output) => {
370 result.push_str(&output);
371 }
372 Err(_) => {
373 result.push_str("$(");
375 result.push_str(&sub_command);
376 result.push(')');
377 }
378 }
379 }
380 Err(_parse_err) => {
381 let tokens_str = sub_command.trim();
383 if tokens_str.contains(' ') {
384 let parts: Vec<&str> = tokens_str.split_whitespace().collect();
386 if let Some(first_token) = parts.first()
387 && shell_state.get_function(first_token).is_some()
388 {
389 let function_call = Ast::FunctionCall {
391 name: first_token.to_string(),
392 args: parts[1..].iter().map(|s| s.to_string()).collect(),
393 };
394 match execute_and_capture_output(function_call, shell_state) {
395 Ok(output) => {
396 result.push_str(&output);
397 continue;
398 }
399 Err(_) => {
400 }
402 }
403 }
404 }
405 result.push_str("$(");
407 result.push_str(&sub_command);
408 result.push(')');
409 }
410 }
411 } else {
412 result.push_str("$(");
414 result.push_str(&sub_command);
415 result.push(')');
416 }
417 } else {
418 let mut var_name = String::new();
420 let mut next_ch = chars.peek();
421
422 if let Some(&c) = next_ch {
424 if c == '?' || c == '$' || c == '0' || c == '#' || c == '*' || c == '@' {
425 var_name.push(c);
426 chars.next(); } else if c.is_ascii_digit() {
428 var_name.push(c);
430 chars.next();
431 } else {
432 while let Some(&c) = next_ch {
434 if c.is_alphanumeric() || c == '_' {
435 var_name.push(c);
436 chars.next(); next_ch = chars.peek();
438 } else {
439 break;
440 }
441 }
442 }
443 }
444
445 if !var_name.is_empty() {
446 if let Some(value) = shell_state.get_var(&var_name) {
447 result.push_str(&value);
448 } else {
449 if var_name.chars().next().unwrap().is_ascii_digit()
452 || var_name == "?"
453 || var_name == "$"
454 || var_name == "0"
455 || var_name == "#"
456 || var_name == "*"
457 || var_name == "@"
458 {
459 } else {
461 result.push('$');
463 result.push_str(&var_name);
464 }
465 }
466 } else {
467 result.push('$');
468 }
469 }
470 } else if ch == '`' {
471 let mut sub_command = String::new();
473
474 for c in chars.by_ref() {
475 if c == '`' {
476 break;
477 }
478 sub_command.push(c);
479 }
480
481 if let Ok(tokens) = crate::lexer::lex(&sub_command, shell_state) {
483 let expanded_tokens = match crate::lexer::expand_aliases(
485 tokens,
486 shell_state,
487 &mut std::collections::HashSet::new(),
488 ) {
489 Ok(t) => t,
490 Err(_) => {
491 result.push('`');
493 result.push_str(&sub_command);
494 result.push('`');
495 continue;
496 }
497 };
498
499 if let Ok(ast) = crate::parser::parse(expanded_tokens) {
500 match execute_and_capture_output(ast, shell_state) {
502 Ok(output) => {
503 result.push_str(&output);
504 }
505 Err(_) => {
506 result.push('`');
508 result.push_str(&sub_command);
509 result.push('`');
510 }
511 }
512 } else {
513 result.push('`');
515 result.push_str(&sub_command);
516 result.push('`');
517 }
518 } else {
519 result.push('`');
521 result.push_str(&sub_command);
522 result.push('`');
523 }
524 } else {
525 result.push(ch);
526 }
527 }
528
529 result
530}
531
532fn expand_wildcards(args: &[String]) -> Result<Vec<String>, String> {
533 let mut expanded_args = Vec::new();
534
535 for arg in args {
536 if arg.contains('*') || arg.contains('?') || arg.contains('[') {
537 match glob::glob(arg) {
539 Ok(paths) => {
540 let mut matches: Vec<String> = paths
541 .filter_map(|p| p.ok())
542 .map(|p| p.to_string_lossy().to_string())
543 .collect();
544 if matches.is_empty() {
545 expanded_args.push(arg.clone());
547 } else {
548 matches.sort();
550 expanded_args.extend(matches);
551 }
552 }
553 Err(_e) => {
554 expanded_args.push(arg.clone());
556 }
557 }
558 } else {
559 expanded_args.push(arg.clone());
560 }
561 }
562 Ok(expanded_args)
563}
564
565fn collect_here_document_content(delimiter: &str, shell_state: &mut ShellState) -> String {
569 if let Some(content) = shell_state.pending_heredoc_content.take() {
571 return content;
572 }
573
574 let stdin = std::io::stdin();
576 let mut reader = BufReader::new(stdin.lock());
577 let mut content = String::new();
578 let mut line = String::new();
579
580 loop {
581 line.clear();
582 match reader.read_line(&mut line) {
583 Ok(0) => {
584 break;
586 }
587 Ok(_) => {
588 let line_content = line.trim_end();
590 if line_content == delimiter {
591 break;
593 } else {
594 content.push_str(&line);
596 }
597 }
598 Err(e) => {
599 if shell_state.colors_enabled {
600 eprintln!(
601 "{}Error reading here-document content: {}\x1b[0m",
602 shell_state.color_scheme.error, e
603 );
604 } else {
605 eprintln!("Error reading here-document content: {}", e);
606 }
607 break;
608 }
609 }
610 }
611
612 content
613}
614
615fn apply_redirections(
626 redirections: &[Redirection],
627 shell_state: &mut ShellState,
628 mut command: Option<&mut Command>,
629) -> Result<(), String> {
630 for redir in redirections {
632 match redir {
633 Redirection::Input(file) => {
634 apply_input_redirection(0, file, shell_state, command.as_deref_mut())?;
635 }
636 Redirection::Output(file) => {
637 apply_output_redirection(1, file, false, shell_state, command.as_deref_mut())?;
638 }
639 Redirection::Append(file) => {
640 apply_output_redirection(1, file, true, shell_state, command.as_deref_mut())?;
641 }
642 Redirection::FdInput(fd, file) => {
643 apply_input_redirection(*fd, file, shell_state, command.as_deref_mut())?;
644 }
645 Redirection::FdOutput(fd, file) => {
646 apply_output_redirection(*fd, file, false, shell_state, command.as_deref_mut())?;
647 }
648 Redirection::FdAppend(fd, file) => {
649 apply_output_redirection(*fd, file, true, shell_state, command.as_deref_mut())?;
650 }
651 Redirection::FdDuplicate(target_fd, source_fd) => {
652 apply_fd_duplication(*target_fd, *source_fd, shell_state, command.as_deref_mut())?;
653 }
654 Redirection::FdClose(fd) => {
655 apply_fd_close(*fd, shell_state, command.as_deref_mut())?;
656 }
657 Redirection::FdInputOutput(fd, file) => {
658 apply_fd_input_output(*fd, file, shell_state, command.as_deref_mut())?;
659 }
660 Redirection::HereDoc(delimiter, quoted_str) => {
661 let quoted = quoted_str == "true";
662 apply_heredoc_redirection(
663 0,
664 delimiter,
665 quoted,
666 shell_state,
667 command.as_deref_mut(),
668 )?;
669 }
670 Redirection::HereString(content) => {
671 apply_herestring_redirection(0, content, shell_state, command.as_deref_mut())?;
672 }
673 }
674 }
675 Ok(())
676}
677
678fn apply_input_redirection(
680 fd: i32,
681 file: &str,
682 shell_state: &mut ShellState,
683 command: Option<&mut Command>,
684) -> Result<(), String> {
685 let expanded_file = expand_variables_in_string(file, shell_state);
686
687 let file_handle =
689 File::open(&expanded_file).map_err(|e| format!("Cannot open {}: {}", expanded_file, e))?;
690
691 if fd == 0 {
692 if let Some(cmd) = command {
694 cmd.stdin(Stdio::from(file_handle));
695 } else {
696 shell_state.fd_table.borrow_mut().open_fd(
698 0,
699 &expanded_file,
700 true, false, false, false, )?;
705
706 let raw_fd = shell_state.fd_table.borrow().get_raw_fd(0);
708 if let Some(rfd) = raw_fd {
709 if rfd != 0 {
710 unsafe {
711 if libc::dup2(rfd, 0) < 0 {
712 return Err(format!("Failed to dup2 fd {} to 0", rfd));
713 }
714 }
715 }
716 }
717 }
718 } else {
719 let fd_file = File::open(&expanded_file)
722 .map_err(|e| format!("Cannot open {}: {}", expanded_file, e))?;
723
724 shell_state.fd_table.borrow_mut().open_fd(
726 fd,
727 &expanded_file,
728 true, false, false, false, )?;
733
734 if let Some(cmd) = command {
736 let target_fd = fd;
739 unsafe {
740 cmd.pre_exec(move || {
741 let raw_fd = fd_file.as_raw_fd();
742
743 if raw_fd != target_fd {
746 let result = libc::dup2(raw_fd, target_fd);
747 if result < 0 {
748 return Err(std::io::Error::last_os_error());
749 }
750 }
753 Ok(())
754 });
755 }
756 }
757 }
758
759 Ok(())
760}
761
762fn apply_output_redirection(
764 fd: i32,
765 file: &str,
766 append: bool,
767 shell_state: &mut ShellState,
768 command: Option<&mut Command>,
769) -> Result<(), String> {
770 let expanded_file = expand_variables_in_string(file, shell_state);
771
772 let file_handle = if append {
774 OpenOptions::new()
775 .append(true)
776 .create(true)
777 .open(&expanded_file)
778 .map_err(|e| format!("Cannot open {}: {}", expanded_file, e))?
779 } else {
780 File::create(&expanded_file)
781 .map_err(|e| format!("Cannot create {}: {}", expanded_file, e))?
782 };
783
784 if let Some(cmd) = command {
785 if fd == 1 {
786 cmd.stdout(Stdio::from(file_handle));
788 } else if fd == 2 {
789 cmd.stderr(Stdio::from(file_handle));
791 } else {
792 shell_state.fd_table.borrow_mut().open_fd(
797 fd,
798 &expanded_file,
799 false, true, append,
802 !append, )?;
804 }
805 } else {
806 shell_state.fd_table.borrow_mut().open_fd(
809 fd,
810 &expanded_file,
811 false, true, append,
814 !append, )?;
816
817 let raw_fd = shell_state.fd_table.borrow().get_raw_fd(fd);
820 if let Some(rfd) = raw_fd {
821 if rfd != fd {
823 unsafe {
824 if libc::dup2(rfd, fd) < 0 {
825 return Err(format!("Failed to dup2 fd {} to {}", rfd, fd));
826 }
827 }
828 }
829 }
830 }
831
832 Ok(())
833}
834
835fn apply_fd_duplication(
837 target_fd: i32,
838 source_fd: i32,
839 shell_state: &mut ShellState,
840 _command: Option<&mut Command>,
841) -> Result<(), String> {
842 if shell_state.fd_table.borrow().is_closed(source_fd) {
844 let error_msg = format!("File descriptor {} is closed", source_fd);
845 if shell_state.colors_enabled {
846 eprintln!(
847 "{}Redirection error: {}\x1b[0m",
848 shell_state.color_scheme.error, error_msg
849 );
850 } else {
851 eprintln!("Redirection error: {}", error_msg);
852 }
853 return Err(error_msg);
854 }
855
856 shell_state
858 .fd_table
859 .borrow_mut()
860 .duplicate_fd(source_fd, target_fd)?;
861 Ok(())
862}
863
864fn apply_fd_close(
866 fd: i32,
867 shell_state: &mut ShellState,
868 command: Option<&mut Command>,
869) -> Result<(), String> {
870 shell_state.fd_table.borrow_mut().close_fd(fd)?;
872
873 if let Some(cmd) = command {
876 match fd {
877 0 => {
878 cmd.stdin(Stdio::null());
880 }
881 1 => {
882 cmd.stdout(Stdio::null());
884 }
885 2 => {
886 cmd.stderr(Stdio::null());
888 }
889 _ => {
890 }
893 }
894 }
895
896 Ok(())
897}
898
899fn apply_fd_input_output(
901 fd: i32,
902 file: &str,
903 shell_state: &mut ShellState,
904 _command: Option<&mut Command>,
905) -> Result<(), String> {
906 let expanded_file = expand_variables_in_string(file, shell_state);
907
908 shell_state.fd_table.borrow_mut().open_fd(
910 fd,
911 &expanded_file,
912 true, true, false, false, )?;
917
918 Ok(())
919}
920
921fn apply_heredoc_redirection(
923 fd: i32,
924 delimiter: &str,
925 quoted: bool,
926 shell_state: &mut ShellState,
927 command: Option<&mut Command>,
928) -> Result<(), String> {
929 let here_doc_content = collect_here_document_content(delimiter, shell_state);
930
931 let expanded_content = if quoted {
933 here_doc_content
934 } else {
935 expand_variables_in_string(&here_doc_content, shell_state)
936 };
937
938 let (reader, mut writer) =
940 pipe().map_err(|e| format!("Failed to create pipe for here-document: {}", e))?;
941
942 writeln!(writer, "{}", expanded_content)
943 .map_err(|e| format!("Failed to write here-document content: {}", e))?;
944
945 if fd == 0 {
947 if let Some(cmd) = command {
948 cmd.stdin(Stdio::from(reader));
949 }
950 }
951
952 Ok(())
953}
954
955fn apply_herestring_redirection(
957 fd: i32,
958 content: &str,
959 shell_state: &mut ShellState,
960 command: Option<&mut Command>,
961) -> Result<(), String> {
962 let expanded_content = expand_variables_in_string(content, shell_state);
963
964 let (reader, mut writer) =
966 pipe().map_err(|e| format!("Failed to create pipe for here-string: {}", e))?;
967
968 write!(writer, "{}", expanded_content)
969 .map_err(|e| format!("Failed to write here-string content: {}", e))?;
970
971 if fd == 0 {
973 if let Some(cmd) = command {
974 cmd.stdin(Stdio::from(reader));
975 }
976 }
977
978 Ok(())
979}
980
981pub fn execute_trap_handler(trap_cmd: &str, shell_state: &mut ShellState) -> i32 {
984 let saved_exit_code = shell_state.last_exit_code;
986
987 let result = match crate::lexer::lex(trap_cmd, shell_state) {
993 Ok(tokens) => {
994 match crate::lexer::expand_aliases(
995 tokens,
996 shell_state,
997 &mut std::collections::HashSet::new(),
998 ) {
999 Ok(expanded_tokens) => {
1000 match crate::parser::parse(expanded_tokens) {
1001 Ok(ast) => execute(ast, shell_state),
1002 Err(_) => {
1003 saved_exit_code
1005 }
1006 }
1007 }
1008 Err(_) => {
1009 saved_exit_code
1011 }
1012 }
1013 }
1014 Err(_) => {
1015 saved_exit_code
1017 }
1018 };
1019
1020 shell_state.last_exit_code = saved_exit_code;
1022
1023 result
1024}
1025
1026pub fn execute(ast: Ast, shell_state: &mut ShellState) -> i32 {
1027 match ast {
1028 Ast::Assignment { var, value } => {
1029 let expanded_value = expand_variables_in_string(&value, shell_state);
1031 shell_state.set_var(&var, expanded_value);
1032 0
1033 }
1034 Ast::LocalAssignment { var, value } => {
1035 let expanded_value = expand_variables_in_string(&value, shell_state);
1037 shell_state.set_local_var(&var, expanded_value);
1038 0
1039 }
1040 Ast::Pipeline(commands) => {
1041 if commands.is_empty() {
1042 return 0;
1043 }
1044
1045 if commands.len() == 1 {
1046 execute_single_command(&commands[0], shell_state)
1048 } else {
1049 execute_pipeline(&commands, shell_state)
1051 }
1052 }
1053 Ast::Sequence(asts) => {
1054 let mut exit_code = 0;
1055 for ast in asts {
1056 exit_code = execute(ast, shell_state);
1057
1058 if shell_state.is_returning() {
1060 return exit_code;
1061 }
1062
1063 if shell_state.exit_requested {
1065 return shell_state.exit_code;
1066 }
1067
1068 if shell_state.is_breaking() || shell_state.is_continuing() {
1070 return exit_code;
1071 }
1072 }
1073 exit_code
1074 }
1075 Ast::If {
1076 branches,
1077 else_branch,
1078 } => {
1079 for (condition, then_branch) in branches {
1080 let cond_exit = execute(*condition, shell_state);
1081 if cond_exit == 0 {
1082 let exit_code = execute(*then_branch, shell_state);
1083
1084 if shell_state.is_returning() {
1086 return exit_code;
1087 }
1088
1089 return exit_code;
1090 }
1091 }
1092 if let Some(else_b) = else_branch {
1093 let exit_code = execute(*else_b, shell_state);
1094
1095 if shell_state.is_returning() {
1097 return exit_code;
1098 }
1099
1100 exit_code
1101 } else {
1102 0
1103 }
1104 }
1105 Ast::Case {
1106 word,
1107 cases,
1108 default,
1109 } => {
1110 for (patterns, branch) in cases {
1111 for pattern in &patterns {
1112 if let Ok(glob_pattern) = glob::Pattern::new(pattern) {
1113 if glob_pattern.matches(&word) {
1114 let exit_code = execute(branch, shell_state);
1115
1116 if shell_state.is_returning() {
1118 return exit_code;
1119 }
1120
1121 return exit_code;
1122 }
1123 } else {
1124 if &word == pattern {
1126 let exit_code = execute(branch, shell_state);
1127
1128 if shell_state.is_returning() {
1130 return exit_code;
1131 }
1132
1133 return exit_code;
1134 }
1135 }
1136 }
1137 }
1138 if let Some(def) = default {
1139 let exit_code = execute(*def, shell_state);
1140
1141 if shell_state.is_returning() {
1143 return exit_code;
1144 }
1145
1146 exit_code
1147 } else {
1148 0
1149 }
1150 }
1151 Ast::For {
1152 variable,
1153 items,
1154 body,
1155 } => {
1156 let mut exit_code = 0;
1157
1158 shell_state.enter_loop();
1160
1161 let mut expanded_items = Vec::new();
1163 for item in items {
1164 let expanded = expand_variables_in_string(&item, shell_state);
1166
1167 for word in expanded.split_whitespace() {
1170 expanded_items.push(word.to_string());
1171 }
1172 }
1173
1174 for item in expanded_items {
1176 crate::state::process_pending_signals(shell_state);
1178
1179 if shell_state.exit_requested {
1181 shell_state.exit_loop();
1182 return shell_state.exit_code;
1183 }
1184
1185 shell_state.set_var(&variable, item.clone());
1187
1188 exit_code = execute(*body.clone(), shell_state);
1190
1191 if shell_state.is_returning() {
1193 shell_state.exit_loop();
1194 return exit_code;
1195 }
1196
1197 if shell_state.exit_requested {
1199 shell_state.exit_loop();
1200 return shell_state.exit_code;
1201 }
1202
1203 if shell_state.is_breaking() {
1205 if shell_state.get_break_level() == 1 {
1206 shell_state.clear_break();
1208 break;
1209 } else {
1210 shell_state.decrement_break_level();
1212 break;
1213 }
1214 }
1215
1216 if shell_state.is_continuing() {
1218 if shell_state.get_continue_level() == 1 {
1219 shell_state.clear_continue();
1221 continue;
1222 } else {
1223 shell_state.decrement_continue_level();
1225 break; }
1227 }
1228 }
1229
1230 shell_state.exit_loop();
1232
1233 exit_code
1234 }
1235 Ast::While { condition, body } => {
1236 let mut exit_code = 0;
1237
1238 shell_state.enter_loop();
1240
1241 loop {
1243 let cond_exit = execute(*condition.clone(), shell_state);
1245
1246 if shell_state.is_returning() {
1248 shell_state.exit_loop();
1249 return cond_exit;
1250 }
1251
1252 if shell_state.exit_requested {
1254 shell_state.exit_loop();
1255 return shell_state.exit_code;
1256 }
1257
1258 if cond_exit != 0 {
1260 break;
1261 }
1262
1263 exit_code = execute(*body.clone(), shell_state);
1265
1266 if shell_state.is_returning() {
1268 shell_state.exit_loop();
1269 return exit_code;
1270 }
1271
1272 if shell_state.exit_requested {
1274 shell_state.exit_loop();
1275 return shell_state.exit_code;
1276 }
1277
1278 if shell_state.is_breaking() {
1280 if shell_state.get_break_level() == 1 {
1281 shell_state.clear_break();
1283 break;
1284 } else {
1285 shell_state.decrement_break_level();
1287 break;
1288 }
1289 }
1290
1291 if shell_state.is_continuing() {
1293 if shell_state.get_continue_level() == 1 {
1294 shell_state.clear_continue();
1296 continue;
1297 } else {
1298 shell_state.decrement_continue_level();
1300 break; }
1302 }
1303 }
1304
1305 shell_state.exit_loop();
1307
1308 exit_code
1309 }
1310 Ast::Until { condition, body } => {
1311 let mut exit_code = 0;
1312
1313 shell_state.enter_loop();
1315
1316 loop {
1318 let cond_exit = execute(*condition.clone(), shell_state);
1320
1321 if shell_state.is_returning() {
1323 shell_state.exit_loop();
1324 return cond_exit;
1325 }
1326
1327 if shell_state.exit_requested {
1329 shell_state.exit_loop();
1330 return shell_state.exit_code;
1331 }
1332
1333 if cond_exit == 0 {
1335 break;
1336 }
1337
1338 exit_code = execute(*body.clone(), shell_state);
1340
1341 if shell_state.is_returning() {
1343 shell_state.exit_loop();
1344 return exit_code;
1345 }
1346
1347 if shell_state.exit_requested {
1349 shell_state.exit_loop();
1350 return shell_state.exit_code;
1351 }
1352
1353 if shell_state.is_breaking() {
1355 if shell_state.get_break_level() == 1 {
1356 shell_state.clear_break();
1358 break;
1359 } else {
1360 shell_state.decrement_break_level();
1362 break;
1363 }
1364 }
1365
1366 if shell_state.is_continuing() {
1368 if shell_state.get_continue_level() == 1 {
1369 shell_state.clear_continue();
1371 continue;
1372 } else {
1373 shell_state.decrement_continue_level();
1375 break; }
1377 }
1378 }
1379
1380 shell_state.exit_loop();
1382
1383 exit_code
1384 }
1385 Ast::FunctionDefinition { name, body } => {
1386 shell_state.define_function(name.clone(), *body);
1388 0
1389 }
1390 Ast::FunctionCall { name, args } => {
1391 if let Some(function_body) = shell_state.get_function(&name).cloned() {
1392 if shell_state.function_depth >= shell_state.max_recursion_depth {
1394 eprintln!(
1395 "Function recursion limit ({}) exceeded",
1396 shell_state.max_recursion_depth
1397 );
1398 return 1;
1399 }
1400
1401 shell_state.enter_function();
1403
1404 let old_positional = shell_state.positional_params.clone();
1406
1407 shell_state.set_positional_params(args.clone());
1409
1410 let exit_code = execute(function_body, shell_state);
1412
1413 if shell_state.is_returning() {
1415 let return_value = shell_state.get_return_value().unwrap_or(0);
1416
1417 shell_state.set_positional_params(old_positional);
1419
1420 shell_state.exit_function();
1422
1423 shell_state.clear_return();
1425
1426 shell_state.last_exit_code = return_value;
1428
1429 return return_value;
1431 }
1432
1433 shell_state.set_positional_params(old_positional);
1435
1436 shell_state.exit_function();
1438
1439 shell_state.last_exit_code = exit_code;
1441
1442 exit_code
1443 } else {
1444 eprintln!("Function '{}' not found", name);
1445 1
1446 }
1447 }
1448 Ast::Return { value } => {
1449 if shell_state.function_depth == 0 {
1451 eprintln!("Return statement outside of function");
1452 return 1;
1453 }
1454
1455 let exit_code = if let Some(ref val) = value {
1457 val.parse::<i32>().unwrap_or(0)
1458 } else {
1459 0
1460 };
1461
1462 shell_state.set_return(exit_code);
1464
1465 exit_code
1467 }
1468 Ast::And { left, right } => {
1469 let left_exit = execute(*left, shell_state);
1471
1472 if shell_state.is_returning() {
1474 return left_exit;
1475 }
1476
1477 if left_exit == 0 {
1479 execute(*right, shell_state)
1480 } else {
1481 left_exit
1482 }
1483 }
1484 Ast::Or { left, right } => {
1485 let left_exit = execute(*left, shell_state);
1487
1488 if shell_state.is_returning() {
1490 return left_exit;
1491 }
1492
1493 if left_exit != 0 {
1495 execute(*right, shell_state)
1496 } else {
1497 left_exit
1498 }
1499 }
1500 Ast::Subshell { body } => execute_subshell(*body, shell_state),
1501 Ast::CommandGroup { body } => execute(*body, shell_state),
1502 }
1503}
1504
1505fn execute_single_command(cmd: &ShellCommand, shell_state: &mut ShellState) -> i32 {
1506 if let Some(ref compound_ast) = cmd.compound {
1508 return execute_compound_with_redirections(compound_ast, shell_state, &cmd.redirections);
1510 }
1511
1512 if cmd.args.is_empty() {
1513 if !cmd.redirections.is_empty() {
1515 if let Err(e) = apply_redirections(&cmd.redirections, shell_state, None) {
1516 if shell_state.colors_enabled {
1517 eprintln!(
1518 "{}Redirection error: {}\x1b[0m",
1519 shell_state.color_scheme.error, e
1520 );
1521 } else {
1522 eprintln!("Redirection error: {}", e);
1523 }
1524 return 1;
1525 }
1526 }
1527 return 0;
1528 }
1529
1530 let var_expanded_args = expand_variables_in_args(&cmd.args, shell_state);
1532 let expanded_args = match expand_wildcards(&var_expanded_args) {
1533 Ok(args) => args,
1534 Err(_) => return 1,
1535 };
1536
1537 if expanded_args.is_empty() {
1538 return 0;
1539 }
1540
1541 if shell_state.get_function(&expanded_args[0]).is_some() {
1543 let function_call = Ast::FunctionCall {
1545 name: expanded_args[0].clone(),
1546 args: expanded_args[1..].to_vec(),
1547 };
1548 return execute(function_call, shell_state);
1549 }
1550
1551 if crate::builtins::is_builtin(&expanded_args[0]) {
1552 let temp_cmd = ShellCommand {
1554 args: expanded_args,
1555 redirections: cmd.redirections.clone(),
1556 compound: None,
1557 };
1558
1559 if let Some(ref capture_buffer) = shell_state.capture_output.clone() {
1561 struct CaptureWriter {
1563 buffer: Rc<RefCell<Vec<u8>>>,
1564 }
1565 impl std::io::Write for CaptureWriter {
1566 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
1567 self.buffer.borrow_mut().extend_from_slice(buf);
1568 Ok(buf.len())
1569 }
1570 fn flush(&mut self) -> std::io::Result<()> {
1571 Ok(())
1572 }
1573 }
1574 let writer = CaptureWriter {
1575 buffer: capture_buffer.clone(),
1576 };
1577 crate::builtins::execute_builtin(&temp_cmd, shell_state, Some(Box::new(writer)))
1578 } else {
1579 crate::builtins::execute_builtin(&temp_cmd, shell_state, None)
1580 }
1581 } else {
1582 let mut env_assignments = Vec::new();
1585 let mut command_start_idx = 0;
1586
1587 for (idx, arg) in expanded_args.iter().enumerate() {
1588 if let Some(eq_pos) = arg.find('=')
1590 && eq_pos > 0
1591 {
1592 let var_part = &arg[..eq_pos];
1593 if var_part
1595 .chars()
1596 .next()
1597 .map(|c| c.is_alphabetic() || c == '_')
1598 .unwrap_or(false)
1599 && var_part.chars().all(|c| c.is_alphanumeric() || c == '_')
1600 {
1601 env_assignments.push(arg.clone());
1602 command_start_idx = idx + 1;
1603 continue;
1604 }
1605 }
1606 break;
1608 }
1609
1610 let has_command = command_start_idx < expanded_args.len();
1612
1613 if !has_command {
1616 for assignment in &env_assignments {
1617 if let Some(eq_pos) = assignment.find('=') {
1618 let var_name = &assignment[..eq_pos];
1619 let var_value = &assignment[eq_pos + 1..];
1620 shell_state.set_var(var_name, var_value.to_string());
1621 }
1622 }
1623
1624 if !cmd.redirections.is_empty() {
1626 if let Err(e) = apply_redirections(&cmd.redirections, shell_state, None) {
1627 if shell_state.colors_enabled {
1628 eprintln!(
1629 "{}Redirection error: {}\x1b[0m",
1630 shell_state.color_scheme.error, e
1631 );
1632 } else {
1633 eprintln!("Redirection error: {}", e);
1634 }
1635 return 1;
1636 }
1637 }
1638 return 0;
1639 }
1640
1641 let mut command = Command::new(&expanded_args[command_start_idx]);
1643 command.args(&expanded_args[command_start_idx + 1..]);
1644
1645 if let Some(fd) = shell_state.stdin_override {
1647 unsafe {
1648 let dup_fd = libc::dup(fd);
1649 if dup_fd >= 0 {
1650 command.stdin(Stdio::from_raw_fd(dup_fd));
1651 }
1652 }
1653 }
1654
1655 let mut child_env = shell_state.get_env_for_child();
1657
1658 for assignment in env_assignments {
1660 if let Some(eq_pos) = assignment.find('=') {
1661 let var_name = assignment[..eq_pos].to_string();
1662 let var_value = assignment[eq_pos + 1..].to_string();
1663 child_env.insert(var_name, var_value);
1664 }
1665 }
1666
1667 command.env_clear();
1668 for (key, value) in child_env {
1669 command.env(key, value);
1670 }
1671
1672 let capturing = shell_state.capture_output.is_some();
1674 if capturing {
1675 command.stdout(Stdio::piped());
1676 }
1677
1678 if let Err(e) = apply_redirections(&cmd.redirections, shell_state, Some(&mut command)) {
1680 if shell_state.colors_enabled {
1681 eprintln!(
1682 "{}Redirection error: {}\x1b[0m",
1683 shell_state.color_scheme.error, e
1684 );
1685 } else {
1686 eprintln!("Redirection error: {}", e);
1687 }
1688 return 1;
1689 }
1690
1691 let custom_fds: Vec<(i32, RawFd)> = {
1695 let fd_table = shell_state.fd_table.borrow();
1696 let mut fds = Vec::new();
1697
1698 for fd_num in 3..=9 {
1699 if fd_table.is_open(fd_num) {
1700 if let Some(raw_fd) = fd_table.get_raw_fd(fd_num) {
1701 fds.push((fd_num, raw_fd));
1702 }
1703 }
1704 }
1705
1706 fds
1707 };
1708
1709 if !custom_fds.is_empty() {
1711 unsafe {
1712 command.pre_exec(move || {
1713 for (target_fd, source_fd) in &custom_fds {
1714 let result = libc::dup2(*source_fd, *target_fd);
1715 if result < 0 {
1716 return Err(std::io::Error::last_os_error());
1717 }
1718 }
1719 Ok(())
1720 });
1721 }
1722 }
1723
1724 match command.spawn() {
1728 Ok(mut child) => {
1729 if capturing {
1731 if let Some(mut stdout) = child.stdout.take() {
1732 use std::io::Read;
1733 let mut output = Vec::new();
1734 if stdout.read_to_end(&mut output).is_ok() {
1735 if let Some(ref capture_buffer) = shell_state.capture_output {
1736 capture_buffer.borrow_mut().extend_from_slice(&output);
1737 }
1738 }
1739 }
1740 }
1741
1742 match child.wait() {
1743 Ok(status) => status.code().unwrap_or(0),
1744 Err(e) => {
1745 if shell_state.colors_enabled {
1746 eprintln!(
1747 "{}Error waiting for command: {}\x1b[0m",
1748 shell_state.color_scheme.error, e
1749 );
1750 } else {
1751 eprintln!("Error waiting for command: {}", e);
1752 }
1753 1
1754 }
1755 }
1756 }
1757 Err(e) => {
1758 if shell_state.colors_enabled {
1759 eprintln!(
1760 "{}Command spawn error: {}\x1b[0m",
1761 shell_state.color_scheme.error, e
1762 );
1763 } else {
1764 eprintln!("Command spawn error: {}", e);
1765 }
1766 1
1767 }
1768 }
1769 }
1770}
1771
1772fn execute_pipeline(commands: &[ShellCommand], shell_state: &mut ShellState) -> i32 {
1773 let mut exit_code = 0;
1774 let mut previous_stdout: Option<File> = None;
1775
1776 for (i, cmd) in commands.iter().enumerate() {
1777 let is_last = i == commands.len() - 1;
1778
1779 if let Some(ref compound_ast) = cmd.compound {
1780 let (com_exit_code, com_stdout) = execute_compound_in_pipeline(
1782 compound_ast,
1783 shell_state,
1784 previous_stdout.take(),
1785 i == 0,
1786 is_last,
1787 &cmd.redirections,
1788 );
1789 exit_code = com_exit_code;
1790 previous_stdout = com_stdout;
1791 continue;
1792 }
1793
1794 if cmd.args.is_empty() {
1795 continue;
1796 }
1797
1798 let var_expanded_args = expand_variables_in_args(&cmd.args, shell_state);
1800 let expanded_args = match expand_wildcards(&var_expanded_args) {
1801 Ok(args) => args,
1802 Err(_) => return 1,
1803 };
1804
1805 if expanded_args.is_empty() {
1806 continue;
1807 }
1808
1809 if crate::builtins::is_builtin(&expanded_args[0]) {
1810 let temp_cmd = ShellCommand {
1813 args: expanded_args,
1814 redirections: cmd.redirections.clone(),
1815 compound: None,
1816 };
1817 if !is_last {
1818 let (reader, writer) = match pipe() {
1820 Ok((r, w)) => (unsafe { File::from_raw_fd(r.into_raw_fd()) }, w),
1821 Err(e) => {
1822 if shell_state.colors_enabled {
1823 eprintln!(
1824 "{}Error creating pipe for builtin: {}\x1b[0m",
1825 shell_state.color_scheme.error, e
1826 );
1827 } else {
1828 eprintln!("Error creating pipe for builtin: {}", e);
1829 }
1830 return 1;
1831 }
1832 };
1833 exit_code = crate::builtins::execute_builtin(
1835 &temp_cmd,
1836 shell_state,
1837 Some(Box::new(writer)),
1838 );
1839 previous_stdout = Some(reader);
1841 } else {
1842 if let Some(ref capture_buffer) = shell_state.capture_output.clone() {
1844 struct CaptureWriter {
1846 buffer: Rc<RefCell<Vec<u8>>>,
1847 }
1848 impl std::io::Write for CaptureWriter {
1849 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
1850 self.buffer.borrow_mut().extend_from_slice(buf);
1851 Ok(buf.len())
1852 }
1853 fn flush(&mut self) -> std::io::Result<()> {
1854 Ok(())
1855 }
1856 }
1857 let writer = CaptureWriter {
1858 buffer: capture_buffer.clone(),
1859 };
1860 exit_code = crate::builtins::execute_builtin(
1861 &temp_cmd,
1862 shell_state,
1863 Some(Box::new(writer)),
1864 );
1865 } else {
1866 exit_code = crate::builtins::execute_builtin(&temp_cmd, shell_state, None);
1868 }
1869 previous_stdout = None;
1870 }
1871 } else {
1872 let mut command = Command::new(&expanded_args[0]);
1873 command.args(&expanded_args[1..]);
1874
1875 let child_env = shell_state.get_env_for_child();
1877 command.env_clear();
1878 for (key, value) in child_env {
1879 command.env(key, value);
1880 }
1881
1882 if let Some(prev) = previous_stdout.take() {
1884 command.stdin(Stdio::from(prev));
1885 } else if i > 0 {
1886 command.stdin(Stdio::null());
1890 } else if let Some(fd) = shell_state.stdin_override {
1891 unsafe {
1894 let dup_fd = libc::dup(fd);
1895 if dup_fd >= 0 {
1896 command.stdin(Stdio::from_raw_fd(dup_fd));
1897 }
1898 }
1899 }
1900
1901 if !is_last {
1903 command.stdout(Stdio::piped());
1904 } else if shell_state.capture_output.is_some() {
1905 command.stdout(Stdio::piped());
1907 }
1908
1909 if let Err(e) = apply_redirections(&cmd.redirections, shell_state, Some(&mut command)) {
1911 if shell_state.colors_enabled {
1912 eprintln!(
1913 "{}Redirection error: {}\x1b[0m",
1914 shell_state.color_scheme.error, e
1915 );
1916 } else {
1917 eprintln!("Redirection error: {}", e);
1918 }
1919 return 1;
1920 }
1921
1922 match command.spawn() {
1923 Ok(mut child) => {
1924 if !is_last {
1925 previous_stdout = child
1926 .stdout
1927 .take()
1928 .map(|s| unsafe { File::from_raw_fd(s.into_raw_fd()) });
1929 } else if shell_state.capture_output.is_some() {
1930 if let Some(mut stdout) = child.stdout.take() {
1932 use std::io::Read;
1933 let mut output = Vec::new();
1934 if stdout.read_to_end(&mut output).is_ok()
1935 && let Some(ref capture_buffer) = shell_state.capture_output
1936 {
1937 capture_buffer.borrow_mut().extend_from_slice(&output);
1938 }
1939 }
1940 }
1941 match child.wait() {
1942 Ok(status) => {
1943 exit_code = status.code().unwrap_or(0);
1944 }
1945 Err(e) => {
1946 if shell_state.colors_enabled {
1947 eprintln!(
1948 "{}Error waiting for command: {}\x1b[0m",
1949 shell_state.color_scheme.error, e
1950 );
1951 } else {
1952 eprintln!("Error waiting for command: {}", e);
1953 }
1954 exit_code = 1;
1955 }
1956 }
1957 }
1958 Err(e) => {
1959 if shell_state.colors_enabled {
1960 eprintln!(
1961 "{}Error spawning command '{}{}",
1962 shell_state.color_scheme.error,
1963 expanded_args[0],
1964 &format!("': {}\x1b[0m", e)
1965 );
1966 } else {
1967 eprintln!("Error spawning command '{}': {}", expanded_args[0], e);
1968 }
1969 exit_code = 1;
1970 }
1971 }
1972 }
1973 }
1974
1975 exit_code
1976}
1977
1978fn execute_subshell(body: Ast, shell_state: &mut ShellState) -> i32 {
1996 if shell_state.subshell_depth >= MAX_SUBSHELL_DEPTH {
1998 if shell_state.colors_enabled {
1999 eprintln!(
2000 "{}Subshell nesting limit ({}) exceeded\x1b[0m",
2001 shell_state.color_scheme.error, MAX_SUBSHELL_DEPTH
2002 );
2003 } else {
2004 eprintln!("Subshell nesting limit ({}) exceeded", MAX_SUBSHELL_DEPTH);
2005 }
2006 shell_state.last_exit_code = 1;
2007 return 1;
2008 }
2009
2010 let original_dir = std::env::current_dir().ok();
2012
2013 let mut subshell_state = shell_state.clone();
2015
2016 match shell_state.fd_table.borrow().deep_clone() {
2020 Ok(new_fd_table) => {
2021 subshell_state.fd_table = Rc::new(RefCell::new(new_fd_table));
2022 }
2023 Err(e) => {
2024 if shell_state.colors_enabled {
2025 eprintln!(
2026 "{}Failed to clone file descriptor table: {}\x1b[0m",
2027 shell_state.color_scheme.error, e
2028 );
2029 } else {
2030 eprintln!("Failed to clone file descriptor table: {}", e);
2031 }
2032 return 1;
2033 }
2034 }
2035
2036 subshell_state.subshell_depth = shell_state.subshell_depth + 1;
2038
2039 let parent_traps = shell_state.trap_handlers.lock().unwrap().clone();
2041 subshell_state.trap_handlers = std::sync::Arc::new(std::sync::Mutex::new(parent_traps));
2042
2043 let exit_code = execute(body, &mut subshell_state);
2045
2046 let final_exit_code = if subshell_state.exit_requested {
2049 subshell_state.exit_code
2051 } else if subshell_state.is_returning() {
2052 subshell_state.get_return_value().unwrap_or(exit_code)
2055 } else {
2056 exit_code
2057 };
2058
2059 subshell_state.fd_table.borrow_mut().clear();
2062
2063 if let Some(dir) = original_dir {
2065 let _ = std::env::set_current_dir(dir);
2066 }
2067
2068 shell_state.last_exit_code = final_exit_code;
2070
2071 final_exit_code
2073}
2074
2075fn execute_compound_with_redirections(
2085 compound_ast: &Ast,
2086 shell_state: &mut ShellState,
2087 redirections: &[Redirection],
2088) -> i32 {
2089 match compound_ast {
2090 Ast::CommandGroup { body } => {
2091 if let Err(e) = shell_state.fd_table.borrow_mut().save_all_fds() {
2093 eprintln!("Error saving FDs: {}", e);
2094 return 1;
2095 }
2096
2097 if let Err(e) = apply_redirections(redirections, shell_state, None) {
2099 if shell_state.colors_enabled {
2100 eprintln!("{}{}\u{001b}[0m", shell_state.color_scheme.error, e);
2101 } else {
2102 eprintln!("{}", e);
2103 }
2104 shell_state.fd_table.borrow_mut().restore_all_fds().ok();
2105 return 1;
2106 }
2107
2108 let exit_code = execute(*body.clone(), shell_state);
2110
2111 if let Err(e) = shell_state.fd_table.borrow_mut().restore_all_fds() {
2113 eprintln!("Error restoring FDs: {}", e);
2114 }
2115
2116 exit_code
2117 }
2118 Ast::Subshell { body } => {
2119 let has_output_redir = redirections.iter().any(|r| {
2126 matches!(
2127 r,
2128 Redirection::Output(_)
2129 | Redirection::Append(_)
2130 | Redirection::FdOutput(_, _)
2131 | Redirection::FdAppend(_, _)
2132 )
2133 });
2134
2135 if has_output_redir {
2136 let mut subshell_state = shell_state.clone();
2138
2139 let capture_buffer = Rc::new(RefCell::new(Vec::new()));
2141 subshell_state.capture_output = Some(capture_buffer.clone());
2142
2143 let exit_code = execute(*body.clone(), &mut subshell_state);
2145
2146 let output = capture_buffer.borrow().clone();
2148
2149 for redir in redirections {
2151 match redir {
2152 Redirection::Output(file) => {
2153 let expanded_file = expand_variables_in_string(file, shell_state);
2154 if let Err(e) = std::fs::write(&expanded_file, &output) {
2155 if shell_state.colors_enabled {
2156 eprintln!(
2157 "{}Redirection error: {}\x1b[0m",
2158 shell_state.color_scheme.error, e
2159 );
2160 } else {
2161 eprintln!("Redirection error: {}", e);
2162 }
2163 return 1;
2164 }
2165 }
2166 Redirection::Append(file) => {
2167 let expanded_file = expand_variables_in_string(file, shell_state);
2168 use std::fs::OpenOptions;
2169 let mut file_handle = match OpenOptions::new()
2170 .append(true)
2171 .create(true)
2172 .open(&expanded_file)
2173 {
2174 Ok(f) => f,
2175 Err(e) => {
2176 if shell_state.colors_enabled {
2177 eprintln!(
2178 "{}Redirection error: {}\x1b[0m",
2179 shell_state.color_scheme.error, e
2180 );
2181 } else {
2182 eprintln!("Redirection error: {}", e);
2183 }
2184 return 1;
2185 }
2186 };
2187 if let Err(e) = file_handle.write_all(&output) {
2188 if shell_state.colors_enabled {
2189 eprintln!(
2190 "{}Redirection error: {}\x1b[0m",
2191 shell_state.color_scheme.error, e
2192 );
2193 } else {
2194 eprintln!("Redirection error: {}", e);
2195 }
2196 return 1;
2197 }
2198 }
2199 _ => {
2200 }
2203 }
2204 }
2205
2206 shell_state.last_exit_code = exit_code;
2207 exit_code
2208 } else {
2209 execute_subshell(*body.clone(), shell_state)
2211 }
2212 }
2213 _ => {
2214 eprintln!("Unsupported compound command type");
2215 1
2216 }
2217 }
2218}
2219
2220fn has_stdout_redirection(redirections: &[Redirection]) -> bool {
2223 redirections.iter().any(|r| match r {
2224 Redirection::Output(_) | Redirection::Append(_) => true,
2226 Redirection::FdOutput(1, _) | Redirection::FdAppend(1, _) => true,
2228 Redirection::FdDuplicate(1, _) | Redirection::FdClose(1) => true,
2230 _ => false,
2232 })
2233}
2234
2235fn execute_compound_in_pipeline(
2246 compound_ast: &Ast,
2247 shell_state: &mut ShellState,
2248 stdin: Option<File>,
2249 is_first: bool,
2250 is_last: bool,
2251 redirections: &[Redirection],
2252) -> (i32, Option<File>) {
2253 match compound_ast {
2254 Ast::Subshell { body } | Ast::CommandGroup { body } => {
2255 let mut subshell_state = shell_state.clone();
2257
2258 let mut _stdin_file = stdin;
2261
2262 if let Some(ref f) = _stdin_file {
2263 let fd = f.as_raw_fd();
2264 subshell_state.stdin_override = Some(fd);
2265 } else if !is_first && subshell_state.stdin_override.is_none() {
2266 if let Ok(f) = File::open("/dev/null") {
2268 subshell_state.stdin_override = Some(f.as_raw_fd());
2269 _stdin_file = Some(f);
2270 }
2271 }
2272
2273 let capture_buffer = if (!is_last || shell_state.capture_output.is_some())
2276 && !has_stdout_redirection(redirections)
2277 {
2278 let buffer = Rc::new(RefCell::new(Vec::new()));
2279 subshell_state.capture_output = Some(buffer.clone());
2280 Some(buffer)
2281 } else {
2282 None
2283 };
2284
2285 let exit_code = if matches!(compound_ast, Ast::CommandGroup { .. }) {
2287 if let Err(e) = subshell_state.fd_table.borrow_mut().save_all_fds() {
2289 eprintln!("Error saving FDs: {}", e);
2290 return (1, None);
2291 }
2292
2293 if let Some(ref f) = _stdin_file {
2295 unsafe {
2296 libc::dup2(f.as_raw_fd(), 0);
2297 }
2298 }
2299
2300 if let Err(e) = apply_redirections(redirections, &mut subshell_state, None) {
2302 if subshell_state.colors_enabled {
2303 eprintln!("{}{}\u{001b}[0m", subshell_state.color_scheme.error, e);
2304 } else {
2305 eprintln!("{}", e);
2306 }
2307 subshell_state.fd_table.borrow_mut().restore_all_fds().ok();
2308 return (1, None);
2309 }
2310
2311 let code = execute(*body.clone(), &mut subshell_state);
2313
2314 if let Err(e) = subshell_state.fd_table.borrow_mut().restore_all_fds() {
2316 eprintln!("Error restoring FDs: {}", e);
2317 }
2318 code
2319 } else {
2320 if let Err(e) = subshell_state.fd_table.borrow_mut().save_all_fds() {
2322 eprintln!("Error saving FDs: {}", e);
2323 return (1, None);
2324 }
2325
2326 if let Some(ref f) = _stdin_file {
2328 unsafe {
2329 libc::dup2(f.as_raw_fd(), 0);
2330 }
2331 }
2332
2333 if let Err(e) = apply_redirections(redirections, &mut subshell_state, None) {
2334 eprintln!("{}", e);
2335 subshell_state.fd_table.borrow_mut().restore_all_fds().ok();
2336 return (1, None);
2337 }
2338 let code = execute(*body.clone(), &mut subshell_state);
2339 subshell_state.fd_table.borrow_mut().restore_all_fds().ok();
2340 code
2341 };
2342
2343 let mut next_stdout = None;
2345 if let Some(buffer) = capture_buffer {
2346 let captured = buffer.borrow().clone();
2347
2348 if !is_last {
2350 use std::io::Write;
2351 let (reader, mut writer) = match pipe() {
2352 Ok((r, w)) => (r, w),
2353 Err(e) => {
2354 eprintln!("Error creating pipe for compound command: {}", e);
2355 return (exit_code, None);
2356 }
2357 };
2358 if let Err(e) = writer.write_all(&captured) {
2359 eprintln!("Error writing to pipe: {}", e);
2360 }
2361 drop(writer); next_stdout = Some(unsafe { File::from_raw_fd(reader.into_raw_fd()) });
2364 }
2365
2366 if let Some(ref parent_capture) = shell_state.capture_output {
2368 parent_capture.borrow_mut().extend_from_slice(&captured);
2369 }
2370 }
2371
2372 shell_state.last_exit_code = exit_code;
2373 (exit_code, next_stdout)
2374 }
2375 _ => {
2376 eprintln!("Unsupported compound command in pipeline");
2377 (1, None)
2378 }
2379 }
2380}
2381
2382#[cfg(test)]
2383mod tests {
2384 use super::*;
2385 use std::sync::Mutex;
2386
2387 static ENV_LOCK: Mutex<()> = Mutex::new(());
2389
2390 #[test]
2391 fn test_execute_single_command_builtin() {
2392 let cmd = ShellCommand {
2393 args: vec!["true".to_string()],
2394 redirections: Vec::new(),
2395 compound: None,
2396 };
2397 let mut shell_state = ShellState::new();
2398 let exit_code = execute_single_command(&cmd, &mut shell_state);
2399 assert_eq!(exit_code, 0);
2400 }
2401
2402 #[test]
2404 fn test_execute_single_command_external() {
2405 let cmd = ShellCommand {
2406 args: vec!["true".to_string()], redirections: Vec::new(),
2408 compound: None,
2409 };
2410 let mut shell_state = ShellState::new();
2411 let exit_code = execute_single_command(&cmd, &mut shell_state);
2412 assert_eq!(exit_code, 0);
2413 }
2414
2415 #[test]
2416 fn test_execute_single_command_external_nonexistent() {
2417 let cmd = ShellCommand {
2418 args: vec!["nonexistent_command".to_string()],
2419 redirections: Vec::new(),
2420 compound: None,
2421 };
2422 let mut shell_state = ShellState::new();
2423 let exit_code = execute_single_command(&cmd, &mut shell_state);
2424 assert_eq!(exit_code, 1); }
2426
2427 #[test]
2428 fn test_execute_pipeline() {
2429 let commands = vec![
2430 ShellCommand {
2431 args: vec!["printf".to_string(), "hello".to_string()],
2432 redirections: Vec::new(),
2433 compound: None,
2434 },
2435 ShellCommand {
2436 args: vec!["cat".to_string()], redirections: Vec::new(),
2438 compound: None,
2439 },
2440 ];
2441 let mut shell_state = ShellState::new();
2442 let exit_code = execute_pipeline(&commands, &mut shell_state);
2443 assert_eq!(exit_code, 0);
2444 }
2445
2446 #[test]
2447 fn test_execute_empty_pipeline() {
2448 let commands = vec![];
2449 let mut shell_state = ShellState::new();
2450 let exit_code = execute(Ast::Pipeline(commands), &mut shell_state);
2451 assert_eq!(exit_code, 0);
2452 }
2453
2454 #[test]
2455 fn test_execute_single_command() {
2456 let ast = Ast::Pipeline(vec![ShellCommand {
2457 args: vec!["true".to_string()],
2458 redirections: Vec::new(),
2459 compound: None,
2460 }]);
2461 let mut shell_state = ShellState::new();
2462 let exit_code = execute(ast, &mut shell_state);
2463 assert_eq!(exit_code, 0);
2464 }
2465
2466 #[test]
2467 fn test_execute_function_definition() {
2468 let ast = Ast::FunctionDefinition {
2469 name: "test_func".to_string(),
2470 body: Box::new(Ast::Pipeline(vec![ShellCommand {
2471 args: vec!["echo".to_string(), "hello".to_string()],
2472 redirections: Vec::new(),
2473 compound: None,
2474 }])),
2475 };
2476 let mut shell_state = ShellState::new();
2477 let exit_code = execute(ast, &mut shell_state);
2478 assert_eq!(exit_code, 0);
2479
2480 assert!(shell_state.get_function("test_func").is_some());
2482 }
2483
2484 #[test]
2485 fn test_execute_function_call() {
2486 let mut shell_state = ShellState::new();
2488 shell_state.define_function(
2489 "test_func".to_string(),
2490 Ast::Pipeline(vec![ShellCommand {
2491 args: vec!["echo".to_string(), "hello".to_string()],
2492 redirections: Vec::new(),
2493 compound: None,
2494 }]),
2495 );
2496
2497 let ast = Ast::FunctionCall {
2499 name: "test_func".to_string(),
2500 args: vec![],
2501 };
2502 let exit_code = execute(ast, &mut shell_state);
2503 assert_eq!(exit_code, 0);
2504 }
2505
2506 #[test]
2507 fn test_execute_function_call_with_args() {
2508 let mut shell_state = ShellState::new();
2510 shell_state.define_function(
2511 "test_func".to_string(),
2512 Ast::Pipeline(vec![ShellCommand {
2513 args: vec!["echo".to_string(), "arg1".to_string()],
2514 redirections: Vec::new(),
2515 compound: None,
2516 }]),
2517 );
2518
2519 let ast = Ast::FunctionCall {
2521 name: "test_func".to_string(),
2522 args: vec!["hello".to_string()],
2523 };
2524 let exit_code = execute(ast, &mut shell_state);
2525 assert_eq!(exit_code, 0);
2526 }
2527
2528 #[test]
2529 fn test_execute_nonexistent_function() {
2530 let mut shell_state = ShellState::new();
2531 let ast = Ast::FunctionCall {
2532 name: "nonexistent".to_string(),
2533 args: vec![],
2534 };
2535 let exit_code = execute(ast, &mut shell_state);
2536 assert_eq!(exit_code, 1); }
2538
2539 #[test]
2540 fn test_execute_function_integration() {
2541 let mut shell_state = ShellState::new();
2543
2544 let define_ast = Ast::FunctionDefinition {
2546 name: "hello".to_string(),
2547 body: Box::new(Ast::Pipeline(vec![ShellCommand {
2548 args: vec!["printf".to_string(), "Hello from function".to_string()],
2549 redirections: Vec::new(),
2550 compound: None,
2551 }])),
2552 };
2553 let exit_code = execute(define_ast, &mut shell_state);
2554 assert_eq!(exit_code, 0);
2555
2556 let call_ast = Ast::FunctionCall {
2558 name: "hello".to_string(),
2559 args: vec![],
2560 };
2561 let exit_code = execute(call_ast, &mut shell_state);
2562 assert_eq!(exit_code, 0);
2563 }
2564
2565 #[test]
2566 fn test_execute_function_with_local_variables() {
2567 let mut shell_state = ShellState::new();
2568
2569 shell_state.set_var("global_var", "global_value".to_string());
2571
2572 let define_ast = Ast::FunctionDefinition {
2574 name: "test_func".to_string(),
2575 body: Box::new(Ast::Sequence(vec![
2576 Ast::LocalAssignment {
2577 var: "local_var".to_string(),
2578 value: "local_value".to_string(),
2579 },
2580 Ast::Assignment {
2581 var: "global_var".to_string(),
2582 value: "modified_in_function".to_string(),
2583 },
2584 Ast::Pipeline(vec![ShellCommand {
2585 args: vec!["printf".to_string(), "success".to_string()],
2586 redirections: Vec::new(),
2587 compound: None,
2588 }]),
2589 ])),
2590 };
2591 let exit_code = execute(define_ast, &mut shell_state);
2592 assert_eq!(exit_code, 0);
2593
2594 assert_eq!(
2596 shell_state.get_var("global_var"),
2597 Some("global_value".to_string())
2598 );
2599
2600 let call_ast = Ast::FunctionCall {
2602 name: "test_func".to_string(),
2603 args: vec![],
2604 };
2605 let exit_code = execute(call_ast, &mut shell_state);
2606 assert_eq!(exit_code, 0);
2607
2608 assert_eq!(
2610 shell_state.get_var("global_var"),
2611 Some("modified_in_function".to_string())
2612 );
2613 }
2614
2615 #[test]
2616 fn test_execute_nested_function_calls() {
2617 let mut shell_state = ShellState::new();
2618
2619 shell_state.set_var("global_var", "global".to_string());
2621
2622 let outer_func = Ast::FunctionDefinition {
2624 name: "outer".to_string(),
2625 body: Box::new(Ast::Sequence(vec![
2626 Ast::Assignment {
2627 var: "global_var".to_string(),
2628 value: "outer_modified".to_string(),
2629 },
2630 Ast::FunctionCall {
2631 name: "inner".to_string(),
2632 args: vec![],
2633 },
2634 Ast::Pipeline(vec![ShellCommand {
2635 args: vec!["printf".to_string(), "outer_done".to_string()],
2636 redirections: Vec::new(),
2637 compound: None,
2638 }]),
2639 ])),
2640 };
2641
2642 let inner_func = Ast::FunctionDefinition {
2644 name: "inner".to_string(),
2645 body: Box::new(Ast::Sequence(vec![
2646 Ast::Assignment {
2647 var: "global_var".to_string(),
2648 value: "inner_modified".to_string(),
2649 },
2650 Ast::Pipeline(vec![ShellCommand {
2651 args: vec!["printf".to_string(), "inner_done".to_string()],
2652 redirections: Vec::new(),
2653 compound: None,
2654 }]),
2655 ])),
2656 };
2657
2658 execute(outer_func, &mut shell_state);
2660 execute(inner_func, &mut shell_state);
2661
2662 shell_state.set_var("global_var", "initial".to_string());
2664
2665 let call_ast = Ast::FunctionCall {
2667 name: "outer".to_string(),
2668 args: vec![],
2669 };
2670 let exit_code = execute(call_ast, &mut shell_state);
2671 assert_eq!(exit_code, 0);
2672
2673 assert_eq!(
2676 shell_state.get_var("global_var"),
2677 Some("inner_modified".to_string())
2678 );
2679 }
2680
2681 #[test]
2682 fn test_here_string_execution() {
2683 let cmd = ShellCommand {
2685 args: vec!["cat".to_string()],
2686 redirections: Vec::new(),
2687 compound: None,
2688 };
2690
2691 assert_eq!(cmd.args, vec!["cat"]);
2694 }
2696
2697 #[test]
2698 fn test_here_document_execution() {
2699 let cmd = ShellCommand {
2701 args: vec!["cat".to_string()],
2702 redirections: Vec::new(),
2703 compound: None,
2704 };
2706
2707 assert_eq!(cmd.args, vec!["cat"]);
2710 }
2712
2713 #[test]
2714 fn test_here_document_with_variable_expansion() {
2715 let mut shell_state = ShellState::new();
2717 shell_state.set_var("PWD", "/test/path".to_string());
2718
2719 let content = "Working dir: $PWD";
2721 let expanded = expand_variables_in_string(content, &mut shell_state);
2722
2723 assert_eq!(expanded, "Working dir: /test/path");
2724 }
2725
2726 #[test]
2727 fn test_here_document_with_command_substitution_builtin() {
2728 let mut shell_state = ShellState::new();
2730 shell_state.set_var("PWD", "/test/dir".to_string());
2731
2732 let content = "Current directory: `pwd`";
2734 let expanded = expand_variables_in_string(content, &mut shell_state);
2735
2736 assert!(expanded.contains("Current directory: "));
2738 }
2739
2740 #[test]
2745 fn test_fd_output_redirection() {
2746 let _lock = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
2747
2748 use std::time::{SystemTime, UNIX_EPOCH};
2750 let timestamp = SystemTime::now()
2751 .duration_since(UNIX_EPOCH)
2752 .unwrap()
2753 .as_nanos();
2754 let temp_file = format!("/tmp/rush_test_fd_out_{}.txt", timestamp);
2755
2756 let cmd = ShellCommand {
2758 args: vec![
2759 "sh".to_string(),
2760 "-c".to_string(),
2761 "echo error >&2".to_string(),
2762 ],
2763 redirections: vec![Redirection::FdOutput(2, temp_file.clone())],
2764 compound: None,
2765 };
2766
2767 let mut shell_state = ShellState::new();
2768 let exit_code = execute_single_command(&cmd, &mut shell_state);
2769 assert_eq!(exit_code, 0);
2770
2771 let content = std::fs::read_to_string(&temp_file).unwrap();
2773 assert_eq!(content.trim(), "error");
2774
2775 let _ = std::fs::remove_file(&temp_file);
2777 }
2778
2779 #[test]
2780 fn test_fd_input_redirection() {
2781 let _lock = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
2782
2783 use std::time::{SystemTime, UNIX_EPOCH};
2785 let timestamp = SystemTime::now()
2786 .duration_since(UNIX_EPOCH)
2787 .unwrap()
2788 .as_nanos();
2789 let temp_file = format!("/tmp/rush_test_fd_in_{}.txt", timestamp);
2790
2791 std::fs::write(&temp_file, "test input\n").unwrap();
2792 std::thread::sleep(std::time::Duration::from_millis(10));
2793
2794 let cmd = ShellCommand {
2797 args: vec!["cat".to_string()],
2798 compound: None,
2799 redirections: vec![
2800 Redirection::FdInput(3, temp_file.clone()),
2801 Redirection::Input(temp_file.clone()),
2802 ],
2803 };
2804
2805 let mut shell_state = ShellState::new();
2806 let exit_code = execute_single_command(&cmd, &mut shell_state);
2807 assert_eq!(exit_code, 0);
2808
2809 let _ = std::fs::remove_file(&temp_file);
2811 }
2812
2813 #[test]
2814 fn test_fd_append_redirection() {
2815 let _lock = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
2816
2817 use std::time::{SystemTime, UNIX_EPOCH};
2819 let timestamp = SystemTime::now()
2820 .duration_since(UNIX_EPOCH)
2821 .unwrap()
2822 .as_nanos();
2823 let temp_file = format!("/tmp/rush_test_fd_append_{}.txt", timestamp);
2824
2825 std::fs::write(&temp_file, "first line\n").unwrap();
2826 std::thread::sleep(std::time::Duration::from_millis(10));
2827
2828 let cmd = ShellCommand {
2830 args: vec![
2831 "sh".to_string(),
2832 "-c".to_string(),
2833 "echo second line >&2".to_string(),
2834 ],
2835 redirections: vec![Redirection::FdAppend(2, temp_file.clone())],
2836 compound: None,
2837 };
2838
2839 let mut shell_state = ShellState::new();
2840 let exit_code = execute_single_command(&cmd, &mut shell_state);
2841 assert_eq!(exit_code, 0);
2842
2843 let content = std::fs::read_to_string(&temp_file).unwrap();
2845 assert!(content.contains("first line"));
2846 assert!(content.contains("second line"));
2847
2848 let _ = std::fs::remove_file(&temp_file);
2850 }
2851
2852 #[test]
2853 fn test_fd_duplication_stderr_to_stdout() {
2854 let _lock = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
2855
2856 use std::time::{SystemTime, UNIX_EPOCH};
2858 let timestamp = SystemTime::now()
2859 .duration_since(UNIX_EPOCH)
2860 .unwrap()
2861 .as_nanos();
2862 let temp_file = format!("/tmp/rush_test_fd_dup_{}.txt", timestamp);
2863
2864 let cmd = ShellCommand {
2868 args: vec![
2869 "sh".to_string(),
2870 "-c".to_string(),
2871 "echo test; echo error >&2".to_string(),
2872 ],
2873 compound: None,
2874 redirections: vec![Redirection::Output(temp_file.clone())],
2875 };
2876
2877 let mut shell_state = ShellState::new();
2878 let exit_code = execute_single_command(&cmd, &mut shell_state);
2879 assert_eq!(exit_code, 0);
2880
2881 assert!(std::path::Path::new(&temp_file).exists());
2883 let content = std::fs::read_to_string(&temp_file).unwrap();
2884 assert!(content.contains("test"));
2885
2886 let _ = std::fs::remove_file(&temp_file);
2888 }
2889
2890 #[test]
2891 fn test_fd_close() {
2892 let _lock = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
2893
2894 let cmd = ShellCommand {
2896 args: vec!["sh".to_string(), "-c".to_string(), "echo test".to_string()],
2897 redirections: vec![Redirection::FdClose(2)],
2898 compound: None,
2899 };
2900
2901 let mut shell_state = ShellState::new();
2902 let exit_code = execute_single_command(&cmd, &mut shell_state);
2903 assert_eq!(exit_code, 0);
2904
2905 assert!(shell_state.fd_table.borrow().is_closed(2));
2907 }
2908
2909 #[test]
2910 fn test_fd_read_write() {
2911 let _lock = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
2912
2913 use std::time::{SystemTime, UNIX_EPOCH};
2915 let timestamp = SystemTime::now()
2916 .duration_since(UNIX_EPOCH)
2917 .unwrap()
2918 .as_nanos();
2919 let temp_file = format!("/tmp/rush_test_fd_rw_{}.txt", timestamp);
2920
2921 std::fs::write(&temp_file, "initial content\n").unwrap();
2922 std::thread::sleep(std::time::Duration::from_millis(10));
2923
2924 let cmd = ShellCommand {
2926 args: vec!["cat".to_string()],
2927 compound: None,
2928 redirections: vec![
2929 Redirection::FdInputOutput(3, temp_file.clone()),
2930 Redirection::Input(temp_file.clone()),
2931 ],
2932 };
2933
2934 let mut shell_state = ShellState::new();
2935 let exit_code = execute_single_command(&cmd, &mut shell_state);
2936 assert_eq!(exit_code, 0);
2937
2938 let _ = std::fs::remove_file(&temp_file);
2940 }
2941
2942 #[test]
2943 fn test_multiple_fd_redirections() {
2944 let _lock = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
2945
2946 use std::time::{SystemTime, UNIX_EPOCH};
2948 let timestamp = SystemTime::now()
2949 .duration_since(UNIX_EPOCH)
2950 .unwrap()
2951 .as_nanos();
2952 let out_file = format!("/tmp/rush_test_fd_multi_out_{}.txt", timestamp);
2953 let err_file = format!("/tmp/rush_test_fd_multi_err_{}.txt", timestamp);
2954
2955 let cmd = ShellCommand {
2957 args: vec![
2958 "sh".to_string(),
2959 "-c".to_string(),
2960 "echo stdout; echo stderr >&2".to_string(),
2961 ],
2962 redirections: vec![
2963 Redirection::FdOutput(2, err_file.clone()),
2964 Redirection::Output(out_file.clone()),
2965 ],
2966 compound: None,
2967 };
2968
2969 let mut shell_state = ShellState::new();
2970 let exit_code = execute_single_command(&cmd, &mut shell_state);
2971 assert_eq!(exit_code, 0);
2972
2973 assert!(std::path::Path::new(&out_file).exists());
2975 assert!(std::path::Path::new(&err_file).exists());
2976
2977 let out_content = std::fs::read_to_string(&out_file).unwrap();
2979 let err_content = std::fs::read_to_string(&err_file).unwrap();
2980 assert!(out_content.contains("stdout"));
2981 assert!(err_content.contains("stderr"));
2982
2983 let _ = std::fs::remove_file(&out_file);
2985 let _ = std::fs::remove_file(&err_file);
2986 }
2987
2988 #[test]
2989 fn test_fd_swap_pattern() {
2990 let _lock = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
2991
2992 use std::time::{SystemTime, UNIX_EPOCH};
2994 let timestamp = SystemTime::now()
2995 .duration_since(UNIX_EPOCH)
2996 .unwrap()
2997 .as_nanos();
2998 let temp_file = format!("/tmp/rush_test_fd_swap_{}.txt", timestamp);
2999
3000 let cmd = ShellCommand {
3003 args: vec!["sh".to_string(), "-c".to_string(), "echo test".to_string()],
3004 redirections: vec![
3005 Redirection::FdOutput(3, temp_file.clone()), Redirection::FdClose(3), Redirection::Output(temp_file.clone()), ],
3009 compound: None,
3010 };
3011
3012 let mut shell_state = ShellState::new();
3013 let exit_code = execute_single_command(&cmd, &mut shell_state);
3014 assert_eq!(exit_code, 0);
3015
3016 assert!(shell_state.fd_table.borrow().is_closed(3));
3018
3019 let _ = std::fs::remove_file(&temp_file);
3021 }
3022
3023 #[test]
3024 fn test_fd_redirection_with_pipes() {
3025 let _lock = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
3026
3027 use std::time::{SystemTime, UNIX_EPOCH};
3029 let timestamp = SystemTime::now()
3030 .duration_since(UNIX_EPOCH)
3031 .unwrap()
3032 .as_nanos();
3033 let temp_file = format!("/tmp/rush_test_fd_pipe_{}.txt", timestamp);
3034
3035 let commands = vec![
3038 ShellCommand {
3039 args: vec!["echo".to_string(), "piped output".to_string()],
3040 redirections: vec![],
3041 compound: None,
3042 },
3043 ShellCommand {
3044 args: vec!["cat".to_string()],
3045 compound: None,
3046 redirections: vec![Redirection::Output(temp_file.clone())],
3047 },
3048 ];
3049
3050 let mut shell_state = ShellState::new();
3051 let exit_code = execute_pipeline(&commands, &mut shell_state);
3052 assert_eq!(exit_code, 0);
3053
3054 let content = std::fs::read_to_string(&temp_file).unwrap();
3056 assert!(content.contains("piped output"));
3057
3058 let _ = std::fs::remove_file(&temp_file);
3060 }
3061
3062 #[test]
3063 fn test_fd_error_invalid_fd_number() {
3064 let _lock = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
3065
3066 use std::time::{SystemTime, UNIX_EPOCH};
3068 let timestamp = SystemTime::now()
3069 .duration_since(UNIX_EPOCH)
3070 .unwrap()
3071 .as_nanos();
3072 let temp_file = format!("/tmp/rush_test_fd_invalid_{}.txt", timestamp);
3073
3074 let cmd = ShellCommand {
3076 args: vec!["echo".to_string(), "test".to_string()],
3077 compound: None,
3078 redirections: vec![Redirection::FdOutput(1025, temp_file.clone())],
3079 };
3080
3081 let mut shell_state = ShellState::new();
3082 let exit_code = execute_single_command(&cmd, &mut shell_state);
3083
3084 assert_eq!(exit_code, 1);
3086
3087 let _ = std::fs::remove_file(&temp_file);
3089 }
3090
3091 #[test]
3092 fn test_fd_error_duplicate_closed_fd() {
3093 let _lock = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
3094
3095 let cmd = ShellCommand {
3097 args: vec!["echo".to_string(), "test".to_string()],
3098 compound: None,
3099 redirections: vec![
3100 Redirection::FdClose(3),
3101 Redirection::FdDuplicate(2, 3), ],
3103 };
3104
3105 let mut shell_state = ShellState::new();
3106 let exit_code = execute_single_command(&cmd, &mut shell_state);
3107
3108 assert_eq!(exit_code, 1);
3110 }
3111
3112 #[test]
3113 fn test_fd_error_file_permission() {
3114 let _lock = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
3115
3116 let cmd = ShellCommand {
3118 args: vec!["echo".to_string(), "test".to_string()],
3119 redirections: vec![Redirection::FdOutput(2, "/proc/version".to_string())],
3120 compound: None,
3121 };
3122
3123 let mut shell_state = ShellState::new();
3124 let exit_code = execute_single_command(&cmd, &mut shell_state);
3125
3126 assert_eq!(exit_code, 1);
3128 }
3129
3130 #[test]
3131 fn test_fd_redirection_order() {
3132 let _lock = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
3133
3134 use std::time::{SystemTime, UNIX_EPOCH};
3136 let timestamp = SystemTime::now()
3137 .duration_since(UNIX_EPOCH)
3138 .unwrap()
3139 .as_nanos();
3140 let file1 = format!("/tmp/rush_test_fd_order1_{}.txt", timestamp);
3141 let file2 = format!("/tmp/rush_test_fd_order2_{}.txt", timestamp);
3142
3143 let cmd = ShellCommand {
3146 args: vec!["echo".to_string(), "test".to_string()],
3147 compound: None,
3148 redirections: vec![
3149 Redirection::Output(file1.clone()),
3150 Redirection::Output(file2.clone()),
3151 ],
3152 };
3153
3154 let mut shell_state = ShellState::new();
3155 let exit_code = execute_single_command(&cmd, &mut shell_state);
3156 assert_eq!(exit_code, 0);
3157
3158 let content2 = std::fs::read_to_string(&file2).unwrap();
3160 assert!(content2.contains("test"));
3161
3162 let _ = std::fs::remove_file(&file1);
3164 let _ = std::fs::remove_file(&file2);
3165 }
3166
3167 #[test]
3168 fn test_fd_builtin_with_redirection() {
3169 let _lock = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
3170
3171 use std::time::{SystemTime, UNIX_EPOCH};
3173 let timestamp = SystemTime::now()
3174 .duration_since(UNIX_EPOCH)
3175 .unwrap()
3176 .as_nanos();
3177 let temp_file = format!("/tmp/rush_test_fd_builtin_{}.txt", timestamp);
3178
3179 let cmd = ShellCommand {
3181 args: vec!["echo".to_string(), "builtin test".to_string()],
3182 redirections: vec![Redirection::Output(temp_file.clone())],
3183 compound: None,
3184 };
3185
3186 let mut shell_state = ShellState::new();
3187 let exit_code = execute_single_command(&cmd, &mut shell_state);
3188 assert_eq!(exit_code, 0);
3189
3190 let content = std::fs::read_to_string(&temp_file).unwrap();
3192 assert!(content.contains("builtin test"));
3193
3194 let _ = std::fs::remove_file(&temp_file);
3196 }
3197
3198 #[test]
3199 fn test_fd_variable_expansion_in_filename() {
3200 let _lock = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
3201
3202 use std::time::{SystemTime, UNIX_EPOCH};
3204 let timestamp = SystemTime::now()
3205 .duration_since(UNIX_EPOCH)
3206 .unwrap()
3207 .as_nanos();
3208 let temp_file = format!("/tmp/rush_test_fd_var_{}.txt", timestamp);
3209
3210 let mut shell_state = ShellState::new();
3212 shell_state.set_var("OUTFILE", temp_file.clone());
3213
3214 let cmd = ShellCommand {
3216 args: vec!["echo".to_string(), "variable test".to_string()],
3217 compound: None,
3218 redirections: vec![Redirection::Output("$OUTFILE".to_string())],
3219 };
3220
3221 let exit_code = execute_single_command(&cmd, &mut shell_state);
3222 assert_eq!(exit_code, 0);
3223
3224 let content = std::fs::read_to_string(&temp_file).unwrap();
3226 assert!(content.contains("variable test"));
3227
3228 let _ = std::fs::remove_file(&temp_file);
3230 }
3231
3232 #[test]
3237 fn test_break_in_for_loop() {
3238 let mut shell_state = ShellState::new();
3239 shell_state.set_var("output", "".to_string());
3240
3241 let ast = Ast::For {
3246 variable: "i".to_string(),
3247 items: vec!["1".to_string(), "2".to_string(), "3".to_string(), "4".to_string(), "5".to_string()],
3248 body: Box::new(Ast::Sequence(vec![
3249 Ast::Assignment {
3250 var: "output".to_string(),
3251 value: "$output$i".to_string(),
3252 },
3253 Ast::If {
3254 branches: vec![(
3255 Box::new(Ast::Pipeline(vec![ShellCommand {
3256 args: vec!["test".to_string(), "$i".to_string(), "=".to_string(), "3".to_string()],
3257 redirections: vec![],
3258 compound: None,
3259 }])),
3260 Box::new(Ast::Pipeline(vec![ShellCommand {
3261 args: vec!["break".to_string()],
3262 redirections: vec![],
3263 compound: None,
3264 }])),
3265 )],
3266 else_branch: None,
3267 },
3268 ])),
3269 };
3270
3271 let exit_code = execute(ast, &mut shell_state);
3272 assert_eq!(exit_code, 0);
3273 assert_eq!(shell_state.get_var("output"), Some("123".to_string()));
3274 }
3275
3276 #[test]
3277 fn test_continue_in_for_loop() {
3278 let mut shell_state = ShellState::new();
3279 shell_state.set_var("output", "".to_string());
3280
3281 let ast = Ast::For {
3286 variable: "i".to_string(),
3287 items: vec!["1".to_string(), "2".to_string(), "3".to_string(), "4".to_string(), "5".to_string()],
3288 body: Box::new(Ast::Sequence(vec![
3289 Ast::If {
3290 branches: vec![(
3291 Box::new(Ast::Pipeline(vec![ShellCommand {
3292 args: vec!["test".to_string(), "$i".to_string(), "=".to_string(), "3".to_string()],
3293 redirections: vec![],
3294 compound: None,
3295 }])),
3296 Box::new(Ast::Pipeline(vec![ShellCommand {
3297 args: vec!["continue".to_string()],
3298 redirections: vec![],
3299 compound: None,
3300 }])),
3301 )],
3302 else_branch: None,
3303 },
3304 Ast::Assignment {
3305 var: "output".to_string(),
3306 value: "$output$i".to_string(),
3307 },
3308 ])),
3309 };
3310
3311 let exit_code = execute(ast, &mut shell_state);
3312 assert_eq!(exit_code, 0);
3313 assert_eq!(shell_state.get_var("output"), Some("1245".to_string()));
3314 }
3315
3316 #[test]
3317 fn test_break_in_while_loop() {
3318 let mut shell_state = ShellState::new();
3319 shell_state.set_var("i", "0".to_string());
3320 shell_state.set_var("output", "".to_string());
3321
3322 let ast = Ast::While {
3329 condition: Box::new(Ast::Pipeline(vec![ShellCommand {
3330 args: vec!["test".to_string(), "$i".to_string(), "-lt".to_string(), "10".to_string()],
3331 redirections: vec![],
3332 compound: None,
3333 }])),
3334 body: Box::new(Ast::Sequence(vec![
3335 Ast::Assignment {
3336 var: "i".to_string(),
3337 value: "$((i + 1))".to_string(),
3338 },
3339 Ast::Assignment {
3340 var: "output".to_string(),
3341 value: "$output$i".to_string(),
3342 },
3343 Ast::If {
3344 branches: vec![(
3345 Box::new(Ast::Pipeline(vec![ShellCommand {
3346 args: vec!["test".to_string(), "$i".to_string(), "=".to_string(), "5".to_string()],
3347 redirections: vec![],
3348 compound: None,
3349 }])),
3350 Box::new(Ast::Pipeline(vec![ShellCommand {
3351 args: vec!["break".to_string()],
3352 redirections: vec![],
3353 compound: None,
3354 }])),
3355 )],
3356 else_branch: None,
3357 },
3358 ])),
3359 };
3360
3361 let exit_code = execute(ast, &mut shell_state);
3362 assert_eq!(exit_code, 0);
3363 assert_eq!(shell_state.get_var("output"), Some("12345".to_string()));
3364 }
3365
3366 #[test]
3367 fn test_continue_in_while_loop() {
3368 let mut shell_state = ShellState::new();
3369 shell_state.set_var("i", "0".to_string());
3370 shell_state.set_var("output", "".to_string());
3371
3372 let ast = Ast::While {
3379 condition: Box::new(Ast::Pipeline(vec![ShellCommand {
3380 args: vec!["test".to_string(), "$i".to_string(), "-lt".to_string(), "5".to_string()],
3381 redirections: vec![],
3382 compound: None,
3383 }])),
3384 body: Box::new(Ast::Sequence(vec![
3385 Ast::Assignment {
3386 var: "i".to_string(),
3387 value: "$((i + 1))".to_string(),
3388 },
3389 Ast::If {
3390 branches: vec![(
3391 Box::new(Ast::Pipeline(vec![ShellCommand {
3392 args: vec!["test".to_string(), "$i".to_string(), "=".to_string(), "3".to_string()],
3393 redirections: vec![],
3394 compound: None,
3395 }])),
3396 Box::new(Ast::Pipeline(vec![ShellCommand {
3397 args: vec!["continue".to_string()],
3398 redirections: vec![],
3399 compound: None,
3400 }])),
3401 )],
3402 else_branch: None,
3403 },
3404 Ast::Assignment {
3405 var: "output".to_string(),
3406 value: "$output$i".to_string(),
3407 },
3408 ])),
3409 };
3410
3411 let exit_code = execute(ast, &mut shell_state);
3412 assert_eq!(exit_code, 0);
3413 assert_eq!(shell_state.get_var("output"), Some("1245".to_string()));
3414 }
3415
3416 #[test]
3417 fn test_break_nested_loops() {
3418 let mut shell_state = ShellState::new();
3419 shell_state.set_var("output", "".to_string());
3420
3421 let inner_loop = Ast::For {
3428 variable: "j".to_string(),
3429 items: vec!["a".to_string(), "b".to_string(), "c".to_string()],
3430 body: Box::new(Ast::Sequence(vec![
3431 Ast::Assignment {
3432 var: "output".to_string(),
3433 value: "$output$i$j".to_string(),
3434 },
3435 Ast::If {
3436 branches: vec![(
3437 Box::new(Ast::Pipeline(vec![ShellCommand {
3438 args: vec!["test".to_string(), "$j".to_string(), "=".to_string(), "b".to_string()],
3439 redirections: vec![],
3440 compound: None,
3441 }])),
3442 Box::new(Ast::Pipeline(vec![ShellCommand {
3443 args: vec!["break".to_string()],
3444 redirections: vec![],
3445 compound: None,
3446 }])),
3447 )],
3448 else_branch: None,
3449 },
3450 ])),
3451 };
3452
3453 let outer_loop = Ast::For {
3454 variable: "i".to_string(),
3455 items: vec!["1".to_string(), "2".to_string(), "3".to_string()],
3456 body: Box::new(inner_loop),
3457 };
3458
3459 let exit_code = execute(outer_loop, &mut shell_state);
3460 assert_eq!(exit_code, 0);
3461 assert_eq!(shell_state.get_var("output"), Some("1a1b2a2b3a3b".to_string()));
3462 }
3463
3464 #[test]
3465 fn test_break_2_nested_loops() {
3466 let mut shell_state = ShellState::new();
3467 shell_state.set_var("output", "".to_string());
3468
3469 let inner_loop = Ast::For {
3476 variable: "j".to_string(),
3477 items: vec!["a".to_string(), "b".to_string(), "c".to_string()],
3478 body: Box::new(Ast::Sequence(vec![
3479 Ast::Assignment {
3480 var: "output".to_string(),
3481 value: "$output$i$j".to_string(),
3482 },
3483 Ast::And {
3484 left: Box::new(Ast::Pipeline(vec![ShellCommand {
3485 args: vec!["test".to_string(), "$i".to_string(), "=".to_string(), "2".to_string()],
3486 redirections: vec![],
3487 compound: None,
3488 }])),
3489 right: Box::new(Ast::If {
3490 branches: vec![(
3491 Box::new(Ast::Pipeline(vec![ShellCommand {
3492 args: vec!["test".to_string(), "$j".to_string(), "=".to_string(), "b".to_string()],
3493 redirections: vec![],
3494 compound: None,
3495 }])),
3496 Box::new(Ast::Pipeline(vec![ShellCommand {
3497 args: vec!["break".to_string(), "2".to_string()],
3498 redirections: vec![],
3499 compound: None,
3500 }])),
3501 )],
3502 else_branch: None,
3503 }),
3504 },
3505 ])),
3506 };
3507
3508 let outer_loop = Ast::For {
3509 variable: "i".to_string(),
3510 items: vec!["1".to_string(), "2".to_string(), "3".to_string()],
3511 body: Box::new(inner_loop),
3512 };
3513
3514 let exit_code = execute(outer_loop, &mut shell_state);
3515 assert_eq!(exit_code, 0);
3516 assert_eq!(shell_state.get_var("output"), Some("1a1b1c2a2b".to_string()));
3517 }
3518
3519 #[test]
3520 fn test_continue_nested_loops() {
3521 let mut shell_state = ShellState::new();
3522 shell_state.set_var("output", "".to_string());
3523
3524 let inner_loop = Ast::For {
3531 variable: "j".to_string(),
3532 items: vec!["a".to_string(), "b".to_string(), "c".to_string()],
3533 body: Box::new(Ast::Sequence(vec![
3534 Ast::If {
3535 branches: vec![(
3536 Box::new(Ast::Pipeline(vec![ShellCommand {
3537 args: vec!["test".to_string(), "$j".to_string(), "=".to_string(), "b".to_string()],
3538 redirections: vec![],
3539 compound: None,
3540 }])),
3541 Box::new(Ast::Pipeline(vec![ShellCommand {
3542 args: vec!["continue".to_string()],
3543 redirections: vec![],
3544 compound: None,
3545 }])),
3546 )],
3547 else_branch: None,
3548 },
3549 Ast::Assignment {
3550 var: "output".to_string(),
3551 value: "$output$i$j".to_string(),
3552 },
3553 ])),
3554 };
3555
3556 let outer_loop = Ast::For {
3557 variable: "i".to_string(),
3558 items: vec!["1".to_string(), "2".to_string(), "3".to_string()],
3559 body: Box::new(inner_loop),
3560 };
3561
3562 let exit_code = execute(outer_loop, &mut shell_state);
3563 assert_eq!(exit_code, 0);
3564 assert_eq!(shell_state.get_var("output"), Some("1a1c2a2c3a3c".to_string()));
3565 }
3566
3567 #[test]
3568 fn test_continue_2_nested_loops() {
3569 let mut shell_state = ShellState::new();
3570 shell_state.set_var("output", "".to_string());
3571
3572 let inner_loop = Ast::For {
3580 variable: "j".to_string(),
3581 items: vec!["a".to_string(), "b".to_string(), "c".to_string()],
3582 body: Box::new(Ast::Sequence(vec![
3583 Ast::And {
3584 left: Box::new(Ast::Pipeline(vec![ShellCommand {
3585 args: vec!["test".to_string(), "$i".to_string(), "=".to_string(), "2".to_string()],
3586 redirections: vec![],
3587 compound: None,
3588 }])),
3589 right: Box::new(Ast::If {
3590 branches: vec![(
3591 Box::new(Ast::Pipeline(vec![ShellCommand {
3592 args: vec!["test".to_string(), "$j".to_string(), "=".to_string(), "b".to_string()],
3593 redirections: vec![],
3594 compound: None,
3595 }])),
3596 Box::new(Ast::Pipeline(vec![ShellCommand {
3597 args: vec!["continue".to_string(), "2".to_string()],
3598 redirections: vec![],
3599 compound: None,
3600 }])),
3601 )],
3602 else_branch: None,
3603 }),
3604 },
3605 Ast::Assignment {
3606 var: "output".to_string(),
3607 value: "$output$i$j".to_string(),
3608 },
3609 ])),
3610 };
3611
3612 let outer_loop = Ast::For {
3613 variable: "i".to_string(),
3614 items: vec!["1".to_string(), "2".to_string(), "3".to_string()],
3615 body: Box::new(Ast::Sequence(vec![
3616 inner_loop,
3617 Ast::Assignment {
3618 var: "output".to_string(),
3619 value: "$output$i-".to_string(),
3620 },
3621 ])),
3622 };
3623
3624 let exit_code = execute(outer_loop, &mut shell_state);
3625 assert_eq!(exit_code, 0);
3626 assert_eq!(shell_state.get_var("output"), Some("1a1b1c1-2a3a3b3c3-".to_string()));
3628 }
3629
3630 #[test]
3631 fn test_break_preserves_exit_code() {
3632 let mut shell_state = ShellState::new();
3633
3634 let ast = Ast::For {
3640 variable: "i".to_string(),
3641 items: vec!["1".to_string(), "2".to_string(), "3".to_string()],
3642 body: Box::new(Ast::Sequence(vec![
3643 Ast::Pipeline(vec![ShellCommand {
3644 args: vec!["false".to_string()],
3645 redirections: vec![],
3646 compound: None,
3647 }]),
3648 Ast::Pipeline(vec![ShellCommand {
3649 args: vec!["break".to_string()],
3650 redirections: vec![],
3651 compound: None,
3652 }]),
3653 ])),
3654 };
3655
3656 let exit_code = execute(ast, &mut shell_state);
3657 assert_eq!(exit_code, 0);
3659 }
3660
3661 #[test]
3662 fn test_continue_preserves_exit_code() {
3663 let mut shell_state = ShellState::new();
3664 shell_state.set_var("count", "0".to_string());
3665
3666 let ast = Ast::For {
3672 variable: "i".to_string(),
3673 items: vec!["1".to_string(), "2".to_string()],
3674 body: Box::new(Ast::Sequence(vec![
3675 Ast::Assignment {
3676 var: "count".to_string(),
3677 value: "$((count + 1))".to_string(),
3678 },
3679 Ast::Pipeline(vec![ShellCommand {
3680 args: vec!["false".to_string()],
3681 redirections: vec![],
3682 compound: None,
3683 }]),
3684 Ast::Pipeline(vec![ShellCommand {
3685 args: vec!["continue".to_string()],
3686 redirections: vec![],
3687 compound: None,
3688 }]),
3689 ])),
3690 };
3691
3692 let exit_code = execute(ast, &mut shell_state);
3693 assert_eq!(exit_code, 0);
3695 assert_eq!(shell_state.get_var("count"), Some("2".to_string()));
3696 }
3697
3698 #[test]
3703 fn test_until_basic_loop() {
3704 let mut shell_state = ShellState::new();
3705 shell_state.set_var("i", "0".to_string());
3706 shell_state.set_var("output", "".to_string());
3707
3708 let ast = Ast::Until {
3710 condition: Box::new(Ast::Pipeline(vec![ShellCommand {
3711 args: vec!["test".to_string(), "$i".to_string(), "=".to_string(), "3".to_string()],
3712 redirections: vec![],
3713 compound: None,
3714 }])),
3715 body: Box::new(Ast::Sequence(vec![
3716 Ast::Assignment {
3717 var: "output".to_string(),
3718 value: "$output$i".to_string(),
3719 },
3720 Ast::Assignment {
3721 var: "i".to_string(),
3722 value: "$((i + 1))".to_string(),
3723 },
3724 ])),
3725 };
3726
3727 let exit_code = execute(ast, &mut shell_state);
3728 assert_eq!(exit_code, 0);
3729 assert_eq!(shell_state.get_var("output"), Some("012".to_string()));
3730 assert_eq!(shell_state.get_var("i"), Some("3".to_string()));
3731 }
3732
3733 #[test]
3734 fn test_until_condition_initially_true() {
3735 let mut shell_state = ShellState::new();
3736 shell_state.set_var("executed", "no".to_string());
3737
3738 let ast = Ast::Until {
3740 condition: Box::new(Ast::Pipeline(vec![ShellCommand {
3741 args: vec!["true".to_string()],
3742 redirections: vec![],
3743 compound: None,
3744 }])),
3745 body: Box::new(Ast::Assignment {
3746 var: "executed".to_string(),
3747 value: "yes".to_string(),
3748 }),
3749 };
3750
3751 let exit_code = execute(ast, &mut shell_state);
3752 assert_eq!(exit_code, 0);
3753 assert_eq!(shell_state.get_var("executed"), Some("no".to_string()));
3755 }
3756
3757 #[test]
3758 fn test_until_with_commands_in_body() {
3759 let mut shell_state = ShellState::new();
3760 shell_state.set_var("count", "0".to_string());
3761
3762 let ast = Ast::Until {
3764 condition: Box::new(Ast::Pipeline(vec![ShellCommand {
3765 args: vec!["test".to_string(), "$count".to_string(), "-ge".to_string(), "3".to_string()],
3766 redirections: vec![],
3767 compound: None,
3768 }])),
3769 body: Box::new(Ast::Sequence(vec![
3770 Ast::Assignment {
3771 var: "count".to_string(),
3772 value: "$((count + 1))".to_string(),
3773 },
3774 Ast::Pipeline(vec![ShellCommand {
3775 args: vec!["echo".to_string(), "$count".to_string()],
3776 redirections: vec![],
3777 compound: None,
3778 }]),
3779 ])),
3780 };
3781
3782 let exit_code = execute(ast, &mut shell_state);
3783 assert_eq!(exit_code, 0);
3784 assert_eq!(shell_state.get_var("count"), Some("3".to_string()));
3785 }
3786
3787 #[test]
3788 fn test_until_with_variable_modification() {
3789 let mut shell_state = ShellState::new();
3790 shell_state.set_var("x", "1".to_string());
3791
3792 let ast = Ast::Until {
3794 condition: Box::new(Ast::Pipeline(vec![ShellCommand {
3795 args: vec!["test".to_string(), "$x".to_string(), "-gt".to_string(), "5".to_string()],
3796 redirections: vec![],
3797 compound: None,
3798 }])),
3799 body: Box::new(Ast::Assignment {
3800 var: "x".to_string(),
3801 value: "$((x * 2))".to_string(),
3802 }),
3803 };
3804
3805 let exit_code = execute(ast, &mut shell_state);
3806 assert_eq!(exit_code, 0);
3807 assert_eq!(shell_state.get_var("x"), Some("8".to_string()));
3808 }
3809
3810 #[test]
3811 fn test_until_nested_loops() {
3812 let mut shell_state = ShellState::new();
3813 shell_state.set_var("output", "".to_string());
3814 shell_state.set_var("i", "0".to_string());
3815
3816 let inner_loop = Ast::Until {
3817 condition: Box::new(Ast::Pipeline(vec![ShellCommand {
3818 args: vec!["test".to_string(), "$j".to_string(), "=".to_string(), "2".to_string()],
3819 redirections: vec![],
3820 compound: None,
3821 }])),
3822 body: Box::new(Ast::Sequence(vec![
3823 Ast::Assignment {
3824 var: "output".to_string(),
3825 value: "$output$i$j".to_string(),
3826 },
3827 Ast::Assignment {
3828 var: "j".to_string(),
3829 value: "$((j + 1))".to_string(),
3830 },
3831 ])),
3832 };
3833
3834 let outer_loop = Ast::Until {
3835 condition: Box::new(Ast::Pipeline(vec![ShellCommand {
3836 args: vec!["test".to_string(), "$i".to_string(), "=".to_string(), "2".to_string()],
3837 redirections: vec![],
3838 compound: None,
3839 }])),
3840 body: Box::new(Ast::Sequence(vec![
3841 Ast::Assignment {
3842 var: "i".to_string(),
3843 value: "$((i + 1))".to_string(),
3844 },
3845 Ast::Assignment {
3846 var: "j".to_string(),
3847 value: "0".to_string(),
3848 },
3849 inner_loop,
3850 ])),
3851 };
3852
3853 let exit_code = execute(outer_loop, &mut shell_state);
3854 assert_eq!(exit_code, 0);
3855 assert_eq!(shell_state.get_var("output"), Some("10112021".to_string()));
3856 }
3857
3858 #[test]
3859 fn test_until_with_break() {
3860 let mut shell_state = ShellState::new();
3861 shell_state.set_var("i", "0".to_string());
3862 shell_state.set_var("output", "".to_string());
3863
3864 let ast = Ast::Until {
3865 condition: Box::new(Ast::Pipeline(vec![ShellCommand {
3866 args: vec!["false".to_string()],
3867 redirections: vec![],
3868 compound: None,
3869 }])),
3870 body: Box::new(Ast::Sequence(vec![
3871 Ast::Assignment {
3872 var: "output".to_string(),
3873 value: "$output$i".to_string(),
3874 },
3875 Ast::Assignment {
3876 var: "i".to_string(),
3877 value: "$((i + 1))".to_string(),
3878 },
3879 Ast::If {
3880 branches: vec![(
3881 Box::new(Ast::Pipeline(vec![ShellCommand {
3882 args: vec!["test".to_string(), "$i".to_string(), "=".to_string(), "3".to_string()],
3883 redirections: vec![],
3884 compound: None,
3885 }])),
3886 Box::new(Ast::Pipeline(vec![ShellCommand {
3887 args: vec!["break".to_string()],
3888 redirections: vec![],
3889 compound: None,
3890 }])),
3891 )],
3892 else_branch: None,
3893 },
3894 ])),
3895 };
3896
3897 let exit_code = execute(ast, &mut shell_state);
3898 assert_eq!(exit_code, 0);
3899 assert_eq!(shell_state.get_var("output"), Some("012".to_string()));
3900 }
3901
3902 #[test]
3903 fn test_until_with_continue() {
3904 let mut shell_state = ShellState::new();
3905 shell_state.set_var("i", "0".to_string());
3906 shell_state.set_var("output", "".to_string());
3907
3908 let ast = Ast::Until {
3909 condition: Box::new(Ast::Pipeline(vec![ShellCommand {
3910 args: vec!["test".to_string(), "$i".to_string(), "-ge".to_string(), "5".to_string()],
3911 redirections: vec![],
3912 compound: None,
3913 }])),
3914 body: Box::new(Ast::Sequence(vec![
3915 Ast::Assignment {
3916 var: "i".to_string(),
3917 value: "$((i + 1))".to_string(),
3918 },
3919 Ast::If {
3920 branches: vec![(
3921 Box::new(Ast::Pipeline(vec![ShellCommand {
3922 args: vec!["test".to_string(), "$i".to_string(), "=".to_string(), "3".to_string()],
3923 redirections: vec![],
3924 compound: None,
3925 }])),
3926 Box::new(Ast::Pipeline(vec![ShellCommand {
3927 args: vec!["continue".to_string()],
3928 redirections: vec![],
3929 compound: None,
3930 }])),
3931 )],
3932 else_branch: None,
3933 },
3934 Ast::Assignment {
3935 var: "output".to_string(),
3936 value: "$output$i".to_string(),
3937 },
3938 ])),
3939 };
3940
3941 let exit_code = execute(ast, &mut shell_state);
3942 assert_eq!(exit_code, 0);
3943 assert_eq!(shell_state.get_var("output"), Some("1245".to_string()));
3944 }
3945
3946 #[test]
3947 fn test_until_empty_body() {
3948 let mut shell_state = ShellState::new();
3949 shell_state.set_var("i", "0".to_string());
3950
3951 let ast = Ast::Until {
3953 condition: Box::new(Ast::Pipeline(vec![ShellCommand {
3954 args: vec!["true".to_string()],
3955 redirections: vec![],
3956 compound: None,
3957 }])),
3958 body: Box::new(Ast::Pipeline(vec![ShellCommand {
3959 args: vec!["true".to_string()],
3960 redirections: vec![],
3961 compound: None,
3962 }])),
3963 };
3964
3965 let exit_code = execute(ast, &mut shell_state);
3966 assert_eq!(exit_code, 0);
3967 }
3968
3969 #[test]
3970 fn test_until_with_command_substitution() {
3971 let mut shell_state = ShellState::new();
3972 shell_state.set_var("count", "0".to_string());
3973 shell_state.set_var("output", "".to_string());
3974
3975 let ast = Ast::Until {
3977 condition: Box::new(Ast::Pipeline(vec![ShellCommand {
3978 args: vec!["test".to_string(), "$(echo $count)".to_string(), "=".to_string(), "3".to_string()],
3979 redirections: vec![],
3980 compound: None,
3981 }])),
3982 body: Box::new(Ast::Sequence(vec![
3983 Ast::Assignment {
3984 var: "output".to_string(),
3985 value: "$output$count".to_string(),
3986 },
3987 Ast::Assignment {
3988 var: "count".to_string(),
3989 value: "$((count + 1))".to_string(),
3990 },
3991 ])),
3992 };
3993
3994 let exit_code = execute(ast, &mut shell_state);
3995 assert_eq!(exit_code, 0);
3996 assert_eq!(shell_state.get_var("output"), Some("012".to_string()));
3997 }
3998
3999 #[test]
4000 fn test_until_with_arithmetic_condition() {
4001 let mut shell_state = ShellState::new();
4002 shell_state.set_var("x", "1".to_string());
4003 shell_state.set_var("output", "".to_string());
4004
4005 let ast = Ast::Until {
4007 condition: Box::new(Ast::Pipeline(vec![ShellCommand {
4008 args: vec!["test".to_string(), "$((x * 2))".to_string(), "-gt".to_string(), "10".to_string()],
4009 redirections: vec![],
4010 compound: None,
4011 }])),
4012 body: Box::new(Ast::Sequence(vec![
4013 Ast::Assignment {
4014 var: "output".to_string(),
4015 value: "$output$x".to_string(),
4016 },
4017 Ast::Assignment {
4018 var: "x".to_string(),
4019 value: "$((x + 1))".to_string(),
4020 },
4021 ])),
4022 };
4023
4024 let exit_code = execute(ast, &mut shell_state);
4025 assert_eq!(exit_code, 0);
4026 assert_eq!(shell_state.get_var("output"), Some("12345".to_string()));
4027 }
4028
4029 #[test]
4030 fn test_until_inside_for() {
4031 let mut shell_state = ShellState::new();
4032 shell_state.set_var("output", "".to_string());
4033
4034 let inner_until = Ast::Until {
4036 condition: Box::new(Ast::Pipeline(vec![ShellCommand {
4037 args: vec!["test".to_string(), "$j".to_string(), "=".to_string(), "2".to_string()],
4038 redirections: vec![],
4039 compound: None,
4040 }])),
4041 body: Box::new(Ast::Sequence(vec![
4042 Ast::Assignment {
4043 var: "output".to_string(),
4044 value: "$output$i$j".to_string(),
4045 },
4046 Ast::Assignment {
4047 var: "j".to_string(),
4048 value: "$((j + 1))".to_string(),
4049 },
4050 ])),
4051 };
4052
4053 let outer_for = Ast::For {
4054 variable: "i".to_string(),
4055 items: vec!["1".to_string(), "2".to_string()],
4056 body: Box::new(Ast::Sequence(vec![
4057 Ast::Assignment {
4058 var: "j".to_string(),
4059 value: "0".to_string(),
4060 },
4061 inner_until,
4062 ])),
4063 };
4064
4065 let exit_code = execute(outer_for, &mut shell_state);
4066 assert_eq!(exit_code, 0);
4067 assert_eq!(shell_state.get_var("output"), Some("10112021".to_string()));
4068 }
4069
4070 #[test]
4071 fn test_for_inside_until() {
4072 let mut shell_state = ShellState::new();
4073 shell_state.set_var("output", "".to_string());
4074 shell_state.set_var("i", "0".to_string());
4075
4076 let inner_for = Ast::For {
4078 variable: "j".to_string(),
4079 items: vec!["a".to_string(), "b".to_string()],
4080 body: Box::new(Ast::Assignment {
4081 var: "output".to_string(),
4082 value: "$output$i$j".to_string(),
4083 }),
4084 };
4085
4086 let outer_until = Ast::Until {
4087 condition: Box::new(Ast::Pipeline(vec![ShellCommand {
4088 args: vec!["test".to_string(), "$i".to_string(), "=".to_string(), "2".to_string()],
4089 redirections: vec![],
4090 compound: None,
4091 }])),
4092 body: Box::new(Ast::Sequence(vec![
4093 inner_for,
4094 Ast::Assignment {
4095 var: "i".to_string(),
4096 value: "$((i + 1))".to_string(),
4097 },
4098 ])),
4099 };
4100
4101 let exit_code = execute(outer_until, &mut shell_state);
4102 assert_eq!(exit_code, 0);
4103 assert_eq!(shell_state.get_var("output"), Some("0a0b1a1b".to_string()));
4104 }
4105
4106 #[test]
4107 fn test_until_inside_while() {
4108 let mut shell_state = ShellState::new();
4109 shell_state.set_var("output", "".to_string());
4110 shell_state.set_var("i", "0".to_string());
4111
4112 let inner_until = Ast::Until {
4113 condition: Box::new(Ast::Pipeline(vec![ShellCommand {
4114 args: vec!["test".to_string(), "$j".to_string(), "=".to_string(), "2".to_string()],
4115 redirections: vec![],
4116 compound: None,
4117 }])),
4118 body: Box::new(Ast::Sequence(vec![
4119 Ast::Assignment {
4120 var: "output".to_string(),
4121 value: "$output$i$j".to_string(),
4122 },
4123 Ast::Assignment {
4124 var: "j".to_string(),
4125 value: "$((j + 1))".to_string(),
4126 },
4127 ])),
4128 };
4129
4130 let outer_while = Ast::While {
4131 condition: Box::new(Ast::Pipeline(vec![ShellCommand {
4132 args: vec!["test".to_string(), "$i".to_string(), "-lt".to_string(), "2".to_string()],
4133 redirections: vec![],
4134 compound: None,
4135 }])),
4136 body: Box::new(Ast::Sequence(vec![
4137 Ast::Assignment {
4138 var: "i".to_string(),
4139 value: "$((i + 1))".to_string(),
4140 },
4141 Ast::Assignment {
4142 var: "j".to_string(),
4143 value: "0".to_string(),
4144 },
4145 inner_until,
4146 ])),
4147 };
4148
4149 let exit_code = execute(outer_while, &mut shell_state);
4150 assert_eq!(exit_code, 0);
4151 assert_eq!(shell_state.get_var("output"), Some("10112021".to_string()));
4152 }
4153
4154 #[test]
4155 fn test_while_inside_until() {
4156 let mut shell_state = ShellState::new();
4157 shell_state.set_var("output", "".to_string());
4158 shell_state.set_var("i", "0".to_string());
4159
4160 let inner_while = Ast::While {
4161 condition: Box::new(Ast::Pipeline(vec![ShellCommand {
4162 args: vec!["test".to_string(), "$j".to_string(), "-lt".to_string(), "2".to_string()],
4163 redirections: vec![],
4164 compound: None,
4165 }])),
4166 body: Box::new(Ast::Sequence(vec![
4167 Ast::Assignment {
4168 var: "output".to_string(),
4169 value: "$output$i$j".to_string(),
4170 },
4171 Ast::Assignment {
4172 var: "j".to_string(),
4173 value: "$((j + 1))".to_string(),
4174 },
4175 ])),
4176 };
4177
4178 let outer_until = Ast::Until {
4179 condition: Box::new(Ast::Pipeline(vec![ShellCommand {
4180 args: vec!["test".to_string(), "$i".to_string(), "=".to_string(), "2".to_string()],
4181 redirections: vec![],
4182 compound: None,
4183 }])),
4184 body: Box::new(Ast::Sequence(vec![
4185 Ast::Assignment {
4186 var: "i".to_string(),
4187 value: "$((i + 1))".to_string(),
4188 },
4189 Ast::Assignment {
4190 var: "j".to_string(),
4191 value: "0".to_string(),
4192 },
4193 inner_while,
4194 ])),
4195 };
4196
4197 let exit_code = execute(outer_until, &mut shell_state);
4198 assert_eq!(exit_code, 0);
4199 assert_eq!(shell_state.get_var("output"), Some("10112021".to_string()));
4200 }
4201
4202 #[test]
4203 fn test_until_preserves_exit_code() {
4204 let mut shell_state = ShellState::new();
4205 shell_state.set_var("i", "0".to_string());
4206
4207 let ast = Ast::Until {
4209 condition: Box::new(Ast::Pipeline(vec![ShellCommand {
4210 args: vec!["test".to_string(), "$i".to_string(), "=".to_string(), "1".to_string()],
4211 redirections: vec![],
4212 compound: None,
4213 }])),
4214 body: Box::new(Ast::Sequence(vec![
4215 Ast::Assignment {
4216 var: "i".to_string(),
4217 value: "$((i + 1))".to_string(),
4218 },
4219 Ast::Pipeline(vec![ShellCommand {
4220 args: vec!["false".to_string()],
4221 redirections: vec![],
4222 compound: None,
4223 }]),
4224 ])),
4225 };
4226
4227 let exit_code = execute(ast, &mut shell_state);
4228 assert_eq!(exit_code, 1);
4230 }
4231}