rush_sh/
executor.rs

1use std::cell::RefCell;
2use std::fs::File;
3use std::io::{BufRead, BufReader, pipe};
4use std::process::{Command, Stdio};
5use std::rc::Rc;
6
7use super::parser::{Ast, ShellCommand};
8use super::state::ShellState;
9
10/// Execute a command and capture its output as a string
11/// This is used for command substitution $(...)
12fn execute_and_capture_output(ast: Ast, shell_state: &mut ShellState) -> Result<String, String> {
13    // Create a pipe to capture stdout
14    let (reader, writer) = pipe().map_err(|e| format!("Failed to create pipe: {}", e))?;
15
16    // We need to capture the output, so we'll redirect stdout to our pipe
17    // For builtins, we can pass the writer directly
18    // For external commands, we need to handle them specially
19
20    match &ast {
21        Ast::Pipeline(commands) if commands.len() == 1 => {
22            let cmd = &commands[0];
23            if cmd.args.is_empty() {
24                return Ok(String::new());
25            }
26
27            // Expand variables and wildcards
28            let var_expanded_args = expand_variables_in_args(&cmd.args, shell_state);
29            let expanded_args = expand_wildcards(&var_expanded_args)
30                .map_err(|e| format!("Wildcard expansion failed: {}", e))?;
31
32            if expanded_args.is_empty() {
33                return Ok(String::new());
34            }
35
36            // Check if it's a function call
37            if shell_state.get_function(&expanded_args[0]).is_some() {
38                // Save previous capture state (for nested command substitutions)
39                let previous_capture = shell_state.capture_output.clone();
40
41                // Enable output capture mode
42                let capture_buffer = Rc::new(RefCell::new(Vec::new()));
43                shell_state.capture_output = Some(capture_buffer.clone());
44
45                // Create a FunctionCall AST and execute it
46                let function_call_ast = Ast::FunctionCall {
47                    name: expanded_args[0].clone(),
48                    args: expanded_args[1..].to_vec(),
49                };
50
51                let exit_code = execute(function_call_ast, shell_state);
52
53                // Retrieve captured output
54                let captured = capture_buffer.borrow().clone();
55                let output = String::from_utf8_lossy(&captured).trim_end().to_string();
56
57                // Restore previous capture state
58                shell_state.capture_output = previous_capture;
59
60                if exit_code == 0 {
61                    Ok(output)
62                } else {
63                    Err(format!("Function failed with exit code {}", exit_code))
64                }
65            } else if crate::builtins::is_builtin(&expanded_args[0]) {
66                let temp_cmd = ShellCommand {
67                    args: expanded_args,
68                    input: cmd.input.clone(),
69                    output: None, // We're capturing output
70                    append: None,
71                    here_doc_delimiter: None,
72                    here_string_content: None,
73                };
74
75                // Execute builtin with our writer
76                let exit_code = crate::builtins::execute_builtin(
77                    &temp_cmd,
78                    shell_state,
79                    Some(Box::new(writer)),
80                );
81
82                // Read the captured output
83                drop(temp_cmd); // Ensure writer is dropped
84                let mut output = String::new();
85                use std::io::Read;
86                let mut reader = reader;
87                reader
88                    .read_to_string(&mut output)
89                    .map_err(|e| format!("Failed to read output: {}", e))?;
90
91                if exit_code == 0 {
92                    Ok(output.trim_end().to_string())
93                } else {
94                    Err(format!("Command failed with exit code {}", exit_code))
95                }
96            } else {
97                // External command - execute with output capture
98                drop(writer); // Close writer end before spawning
99
100                let mut command = Command::new(&expanded_args[0]);
101                command.args(&expanded_args[1..]);
102                command.stdout(Stdio::piped());
103                command.stderr(Stdio::null()); // Suppress stderr for command substitution
104
105                // Set environment
106                let child_env = shell_state.get_env_for_child();
107                command.env_clear();
108                for (key, value) in child_env {
109                    command.env(key, value);
110                }
111
112                let output = command
113                    .output()
114                    .map_err(|e| format!("Failed to execute command: {}", e))?;
115
116                if output.status.success() {
117                    Ok(String::from_utf8_lossy(&output.stdout)
118                        .trim_end()
119                        .to_string())
120                } else {
121                    Err(format!(
122                        "Command failed with exit code {}",
123                        output.status.code().unwrap_or(1)
124                    ))
125                }
126            }
127        }
128        _ => {
129            // For complex AST nodes (sequences, pipelines, etc.), we need to execute them
130            // and capture output differently. For now, fall back to executing and capturing
131            // via a temporary approach
132            drop(writer);
133
134            // Execute the AST normally but we can't easily capture output for complex cases
135            // This is a limitation we'll need to address for full support
136            Err("Complex command substitutions not yet fully supported".to_string())
137        }
138    }
139}
140
141fn expand_variables_in_args(args: &[String], shell_state: &mut ShellState) -> Vec<String> {
142    let mut expanded_args = Vec::new();
143
144    for arg in args {
145        // Expand variables within the argument string
146        let expanded_arg = expand_variables_in_string(arg, shell_state);
147        expanded_args.push(expanded_arg);
148    }
149
150    expanded_args
151}
152
153pub fn expand_variables_in_string(input: &str, shell_state: &mut ShellState) -> String {
154    let mut result = String::new();
155    let mut chars = input.chars().peekable();
156
157    while let Some(ch) = chars.next() {
158        if ch == '$' {
159            // Check for command substitution $(...) or arithmetic expansion $((...))
160            if let Some(&'(') = chars.peek() {
161                chars.next(); // consume first (
162
163                // Check if this is arithmetic expansion $((...))
164                if let Some(&'(') = chars.peek() {
165                    // Arithmetic expansion $((...))
166                    chars.next(); // consume second (
167                    let mut arithmetic_expr = String::new();
168                    let mut paren_depth = 1;
169                    let mut found_closing = false;
170
171                    while let Some(c) = chars.next() {
172                        if c == '(' {
173                            paren_depth += 1;
174                            arithmetic_expr.push(c);
175                        } else if c == ')' {
176                            paren_depth -= 1;
177                            if paren_depth == 0 {
178                                // Found the first closing ) - check for second )
179                                if let Some(&')') = chars.peek() {
180                                    chars.next(); // consume the second )
181                                    found_closing = true;
182                                    break;
183                                } else {
184                                    // Missing second closing paren, treat as error
185                                    result.push_str("$((");
186                                    result.push_str(&arithmetic_expr);
187                                    result.push(')');
188                                    break;
189                                }
190                            }
191                            arithmetic_expr.push(c);
192                        } else {
193                            arithmetic_expr.push(c);
194                        }
195                    }
196
197                    if found_closing {
198                        // First expand variables in the arithmetic expression
199                        // The arithmetic evaluator expects variable names without $ prefix
200                        // So we need to expand $VAR to the value before evaluation
201                        let mut expanded_expr = String::new();
202                        let mut expr_chars = arithmetic_expr.chars().peekable();
203
204                        while let Some(ch) = expr_chars.next() {
205                            if ch == '$' {
206                                // Expand variable
207                                let mut var_name = String::new();
208                                if let Some(&c) = expr_chars.peek() {
209                                    if c == '?'
210                                        || c == '$'
211                                        || c == '0'
212                                        || c == '#'
213                                        || c == '*'
214                                        || c == '@'
215                                        || c.is_ascii_digit()
216                                    {
217                                        var_name.push(c);
218                                        expr_chars.next();
219                                    } else {
220                                        while let Some(&c) = expr_chars.peek() {
221                                            if c.is_alphanumeric() || c == '_' {
222                                                var_name.push(c);
223                                                expr_chars.next();
224                                            } else {
225                                                break;
226                                            }
227                                        }
228                                    }
229                                }
230
231                                if !var_name.is_empty() {
232                                    if let Some(value) = shell_state.get_var(&var_name) {
233                                        expanded_expr.push_str(&value);
234                                    } else {
235                                        // Variable not found, use 0 for arithmetic
236                                        expanded_expr.push('0');
237                                    }
238                                } else {
239                                    expanded_expr.push('$');
240                                }
241                            } else {
242                                expanded_expr.push(ch);
243                            }
244                        }
245
246                        match crate::arithmetic::evaluate_arithmetic_expression(
247                            &expanded_expr,
248                            shell_state,
249                        ) {
250                            Ok(value) => {
251                                result.push_str(&value.to_string());
252                            }
253                            Err(e) => {
254                                // On arithmetic error, display a proper error message
255                                if shell_state.colors_enabled {
256                                    result.push_str(&format!(
257                                        "{}arithmetic error: {}{}",
258                                        shell_state.color_scheme.error, e, "\x1b[0m"
259                                    ));
260                                } else {
261                                    result.push_str(&format!("arithmetic error: {}", e));
262                                }
263                            }
264                        }
265                    } else {
266                        // Didn't find proper closing - keep as literal
267                        result.push_str("$((");
268                        result.push_str(&arithmetic_expr);
269                        // Note: we don't add closing parens since they weren't in the input
270                    }
271                    continue;
272                }
273
274                // Regular command substitution $(...)
275                let mut sub_command = String::new();
276                let mut paren_depth = 1;
277
278                for c in chars.by_ref() {
279                    if c == '(' {
280                        paren_depth += 1;
281                        sub_command.push(c);
282                    } else if c == ')' {
283                        paren_depth -= 1;
284                        if paren_depth == 0 {
285                            break;
286                        }
287                        sub_command.push(c);
288                    } else {
289                        sub_command.push(c);
290                    }
291                }
292
293                // Execute the command substitution within the current shell context
294                // Parse and execute the command using our own lexer/parser/executor
295                if let Ok(tokens) = crate::lexer::lex(&sub_command, shell_state) {
296                    // Expand aliases before parsing
297                    let expanded_tokens = match crate::lexer::expand_aliases(
298                        tokens,
299                        shell_state,
300                        &mut std::collections::HashSet::new(),
301                    ) {
302                        Ok(t) => t,
303                        Err(_) => {
304                            // Alias expansion error, keep literal
305                            result.push_str("$(");
306                            result.push_str(&sub_command);
307                            result.push(')');
308                            continue;
309                        }
310                    };
311
312                    if let Ok(ast) = crate::parser::parse(expanded_tokens) {
313                        // Execute within current shell context and capture output
314                        match execute_and_capture_output(ast, shell_state) {
315                            Ok(output) => {
316                                result.push_str(&output);
317                            }
318                            Err(_) => {
319                                // On failure, keep the literal
320                                result.push_str("$(");
321                                result.push_str(&sub_command);
322                                result.push(')');
323                            }
324                        }
325                    } else {
326                        // Parse error - try to handle as function call if it looks like one
327                        let tokens_str = sub_command.trim();
328                        if tokens_str.contains(' ') {
329                            // Split by spaces and check if first token looks like a function call
330                            let parts: Vec<&str> = tokens_str.split_whitespace().collect();
331                            if let Some(first_token) = parts.first()
332                                && shell_state.get_function(first_token).is_some()
333                            {
334                                // This is a function call, create AST manually
335                                let function_call = Ast::FunctionCall {
336                                    name: first_token.to_string(),
337                                    args: parts[1..].iter().map(|s| s.to_string()).collect(),
338                                };
339                                match execute_and_capture_output(function_call, shell_state) {
340                                    Ok(output) => {
341                                        result.push_str(&output);
342                                        continue;
343                                    }
344                                    Err(_) => {
345                                        // Fall back to literal
346                                    }
347                                }
348                            }
349                        }
350                        // Keep the literal
351                        result.push_str("$(");
352                        result.push_str(&sub_command);
353                        result.push(')');
354                    }
355                } else {
356                    // Lex error, keep literal
357                    result.push_str("$(");
358                    result.push_str(&sub_command);
359                    result.push(')');
360                }
361            } else {
362                // Regular variable
363                let mut var_name = String::new();
364                let mut next_ch = chars.peek();
365
366                // Handle special single-character variables first
367                if let Some(&c) = next_ch {
368                    if c == '?' || c == '$' || c == '0' || c == '#' || c == '*' || c == '@' {
369                        var_name.push(c);
370                        chars.next(); // consume the character
371                    } else if c.is_ascii_digit() {
372                        // Positional parameter
373                        var_name.push(c);
374                        chars.next();
375                    } else {
376                        // Regular variable name
377                        while let Some(&c) = next_ch {
378                            if c.is_alphanumeric() || c == '_' {
379                                var_name.push(c);
380                                chars.next(); // consume the character
381                                next_ch = chars.peek();
382                            } else {
383                                break;
384                            }
385                        }
386                    }
387                }
388
389                if !var_name.is_empty() {
390                    if let Some(value) = shell_state.get_var(&var_name) {
391                        result.push_str(&value);
392                    } else {
393                        // Variable not found - for positional parameters, expand to empty string
394                        // For other variables, keep the literal
395                        if var_name.chars().next().unwrap().is_ascii_digit()
396                            || var_name == "?"
397                            || var_name == "$"
398                            || var_name == "0"
399                            || var_name == "#"
400                            || var_name == "*"
401                            || var_name == "@"
402                        {
403                            // Expand to empty string for undefined positional parameters
404                        } else {
405                            // Keep the literal for regular variables
406                            result.push('$');
407                            result.push_str(&var_name);
408                        }
409                    }
410                } else {
411                    result.push('$');
412                }
413            }
414        } else if ch == '`' {
415            // Backtick command substitution
416            let mut sub_command = String::new();
417
418            for c in chars.by_ref() {
419                if c == '`' {
420                    break;
421                }
422                sub_command.push(c);
423            }
424
425            // Execute the command substitution
426            if let Ok(tokens) = crate::lexer::lex(&sub_command, shell_state) {
427                // Expand aliases before parsing
428                let expanded_tokens = match crate::lexer::expand_aliases(
429                    tokens,
430                    shell_state,
431                    &mut std::collections::HashSet::new(),
432                ) {
433                    Ok(t) => t,
434                    Err(_) => {
435                        // Alias expansion error, keep literal
436                        result.push('`');
437                        result.push_str(&sub_command);
438                        result.push('`');
439                        continue;
440                    }
441                };
442
443                if let Ok(ast) = crate::parser::parse(expanded_tokens) {
444                    // Execute and capture output
445                    match execute_and_capture_output(ast, shell_state) {
446                        Ok(output) => {
447                            result.push_str(&output);
448                        }
449                        Err(_) => {
450                            // On failure, keep the literal
451                            result.push('`');
452                            result.push_str(&sub_command);
453                            result.push('`');
454                        }
455                    }
456                } else {
457                    // Parse error, keep literal
458                    result.push('`');
459                    result.push_str(&sub_command);
460                    result.push('`');
461                }
462            } else {
463                // Lex error, keep literal
464                result.push('`');
465                result.push_str(&sub_command);
466                result.push('`');
467            }
468        } else {
469            result.push(ch);
470        }
471    }
472
473    result
474}
475
476fn expand_wildcards(args: &[String]) -> Result<Vec<String>, String> {
477    let mut expanded_args = Vec::new();
478
479    for arg in args {
480        if arg.contains('*') || arg.contains('?') || arg.contains('[') {
481            // Try to expand wildcard
482            match glob::glob(arg) {
483                Ok(paths) => {
484                    let mut matches: Vec<String> = paths
485                        .filter_map(|p| p.ok())
486                        .map(|p| p.to_string_lossy().to_string())
487                        .collect();
488                    if matches.is_empty() {
489                        // No matches, keep literal
490                        expanded_args.push(arg.clone());
491                    } else {
492                        // Sort for consistent behavior
493                        matches.sort();
494                        expanded_args.extend(matches);
495                    }
496                }
497                Err(_e) => {
498                    // Invalid pattern, keep literal
499                    expanded_args.push(arg.clone());
500                }
501            }
502        } else {
503            expanded_args.push(arg.clone());
504        }
505    }
506    Ok(expanded_args)
507}
508
509/// Collect here-document content from stdin until the specified delimiter is found
510/// This function reads from stdin line by line until it finds a line that exactly matches the delimiter
511/// If shell_state has pending_heredoc_content, it uses that instead (for script execution)
512fn collect_here_document_content(delimiter: &str, shell_state: &mut ShellState) -> String {
513    // Check if we have pending here-document content from script execution
514    if let Some(content) = shell_state.pending_heredoc_content.take() {
515        return content;
516    }
517
518    // Otherwise, read from stdin (interactive mode)
519    let stdin = std::io::stdin();
520    let mut reader = BufReader::new(stdin.lock());
521    let mut content = String::new();
522    let mut line = String::new();
523
524    loop {
525        line.clear();
526        match reader.read_line(&mut line) {
527            Ok(0) => {
528                // EOF reached
529                break;
530            }
531            Ok(_) => {
532                // Check if this line (without trailing newline) matches the delimiter
533                let line_content = line.trim_end();
534                if line_content == delimiter {
535                    // Found the delimiter, stop collecting
536                    break;
537                } else {
538                    // This is content, add it to our collection
539                    content.push_str(&line);
540                }
541            }
542            Err(e) => {
543                if shell_state.colors_enabled {
544                    eprintln!(
545                        "{}Error reading here-document content: {}\x1b[0m",
546                        shell_state.color_scheme.error, e
547                    );
548                } else {
549                    eprintln!("Error reading here-document content: {}", e);
550                }
551                break;
552            }
553        }
554    }
555
556    content
557}
558
559/// Execute a trap handler command
560/// Note: Signal masking during trap execution will be added in a future update
561pub fn execute_trap_handler(trap_cmd: &str, shell_state: &mut ShellState) -> i32 {
562    // Save current exit code to preserve it across trap execution
563    let saved_exit_code = shell_state.last_exit_code;
564
565    // TODO: Add signal masking to prevent recursive trap calls
566    // This requires careful handling of the nix sigprocmask API
567    // For now, traps execute without signal masking
568
569    // Parse and execute the trap command
570    let result = match crate::lexer::lex(trap_cmd, shell_state) {
571        Ok(tokens) => {
572            match crate::lexer::expand_aliases(
573                tokens,
574                shell_state,
575                &mut std::collections::HashSet::new(),
576            ) {
577                Ok(expanded_tokens) => {
578                    match crate::parser::parse(expanded_tokens) {
579                        Ok(ast) => execute(ast, shell_state),
580                        Err(_) => {
581                            // Parse error in trap handler - silently continue
582                            saved_exit_code
583                        }
584                    }
585                }
586                Err(_) => {
587                    // Alias expansion error - silently continue
588                    saved_exit_code
589                }
590            }
591        }
592        Err(_) => {
593            // Lex error in trap handler - silently continue
594            saved_exit_code
595        }
596    };
597
598    // Restore the original exit code (trap handlers don't affect $?)
599    shell_state.last_exit_code = saved_exit_code;
600
601    result
602}
603
604pub fn execute(ast: Ast, shell_state: &mut ShellState) -> i32 {
605    match ast {
606        Ast::Assignment { var, value } => {
607            // Expand variables and command substitutions in the value
608            let expanded_value = expand_variables_in_string(&value, shell_state);
609            shell_state.set_var(&var, expanded_value);
610            0
611        }
612        Ast::LocalAssignment { var, value } => {
613            // Expand variables and command substitutions in the value
614            let expanded_value = expand_variables_in_string(&value, shell_state);
615            shell_state.set_local_var(&var, expanded_value);
616            0
617        }
618        Ast::Pipeline(commands) => {
619            if commands.is_empty() {
620                return 0;
621            }
622
623            if commands.len() == 1 {
624                // Single command, handle redirections
625                execute_single_command(&commands[0], shell_state)
626            } else {
627                // Pipeline
628                execute_pipeline(&commands, shell_state)
629            }
630        }
631        Ast::Sequence(asts) => {
632            let mut exit_code = 0;
633            for ast in asts {
634                exit_code = execute(ast, shell_state);
635
636                // Check if we got an early return from a function
637                if shell_state.is_returning() {
638                    return exit_code;
639                }
640
641                // Check if exit was requested (e.g., from trap handler)
642                if shell_state.exit_requested {
643                    return shell_state.exit_code;
644                }
645            }
646            exit_code
647        }
648        Ast::If {
649            branches,
650            else_branch,
651        } => {
652            for (condition, then_branch) in branches {
653                let cond_exit = execute(*condition, shell_state);
654                if cond_exit == 0 {
655                    let exit_code = execute(*then_branch, shell_state);
656
657                    // Check if we got an early return from a function
658                    if shell_state.is_returning() {
659                        return exit_code;
660                    }
661
662                    return exit_code;
663                }
664            }
665            if let Some(else_b) = else_branch {
666                let exit_code = execute(*else_b, shell_state);
667
668                // Check if we got an early return from a function
669                if shell_state.is_returning() {
670                    return exit_code;
671                }
672
673                exit_code
674            } else {
675                0
676            }
677        }
678        Ast::Case {
679            word,
680            cases,
681            default,
682        } => {
683            for (patterns, branch) in cases {
684                for pattern in &patterns {
685                    if let Ok(glob_pattern) = glob::Pattern::new(pattern) {
686                        if glob_pattern.matches(&word) {
687                            let exit_code = execute(branch, shell_state);
688
689                            // Check if we got an early return from a function
690                            if shell_state.is_returning() {
691                                return exit_code;
692                            }
693
694                            return exit_code;
695                        }
696                    } else {
697                        // If pattern is invalid, fall back to exact match
698                        if &word == pattern {
699                            let exit_code = execute(branch, shell_state);
700
701                            // Check if we got an early return from a function
702                            if shell_state.is_returning() {
703                                return exit_code;
704                            }
705
706                            return exit_code;
707                        }
708                    }
709                }
710            }
711            if let Some(def) = default {
712                let exit_code = execute(*def, shell_state);
713
714                // Check if we got an early return from a function
715                if shell_state.is_returning() {
716                    return exit_code;
717                }
718
719                exit_code
720            } else {
721                0
722            }
723        }
724        Ast::For {
725            variable,
726            items,
727            body,
728        } => {
729            let mut exit_code = 0;
730
731            // Execute the loop body for each item
732            for item in items {
733                // Process any pending signals before executing the body
734                crate::state::process_pending_signals(shell_state);
735
736                // Check if exit was requested (e.g., from trap handler)
737                if shell_state.exit_requested {
738                    return shell_state.exit_code;
739                }
740
741                // Set the loop variable
742                shell_state.set_var(&variable, item.clone());
743
744                // Execute the body
745                exit_code = execute(*body.clone(), shell_state);
746
747                // Check if we got an early return from a function
748                if shell_state.is_returning() {
749                    return exit_code;
750                }
751
752                // Check if exit was requested after executing the body
753                if shell_state.exit_requested {
754                    return shell_state.exit_code;
755                }
756            }
757
758            exit_code
759        }
760        Ast::While { condition, body } => {
761            let mut exit_code = 0;
762
763            // Execute the loop while condition is true (exit code 0)
764            loop {
765                // Evaluate the condition
766                let cond_exit = execute(*condition.clone(), shell_state);
767
768                // Check if we got an early return from a function
769                if shell_state.is_returning() {
770                    return cond_exit;
771                }
772
773                // Check if exit was requested (e.g., from trap handler)
774                if shell_state.exit_requested {
775                    return shell_state.exit_code;
776                }
777
778                // If condition is false (non-zero exit code), break
779                if cond_exit != 0 {
780                    break;
781                }
782
783                // Execute the body
784                exit_code = execute(*body.clone(), shell_state);
785
786                // Check if we got an early return from a function
787                if shell_state.is_returning() {
788                    return exit_code;
789                }
790
791                // Check if exit was requested (e.g., from trap handler)
792                if shell_state.exit_requested {
793                    return shell_state.exit_code;
794                }
795            }
796
797            exit_code
798        }
799        Ast::FunctionDefinition { name, body } => {
800            // Store function definition in shell state
801            shell_state.define_function(name.clone(), *body);
802            0
803        }
804        Ast::FunctionCall { name, args } => {
805            if let Some(function_body) = shell_state.get_function(&name).cloned() {
806                // Check recursion limit before entering function
807                if shell_state.function_depth >= shell_state.max_recursion_depth {
808                    eprintln!(
809                        "Function recursion limit ({}) exceeded",
810                        shell_state.max_recursion_depth
811                    );
812                    return 1;
813                }
814
815                // Enter function context for local variable scoping
816                shell_state.enter_function();
817
818                // Set up arguments as regular variables (will be enhanced in Phase 2)
819                let old_positional = shell_state.positional_params.clone();
820
821                // Set positional parameters for function arguments
822                shell_state.set_positional_params(args.clone());
823
824                // Execute function body
825                let exit_code = execute(function_body, shell_state);
826
827                // Check if we got an early return from the function
828                if shell_state.is_returning() {
829                    let return_value = shell_state.get_return_value().unwrap_or(0);
830
831                    // Restore old positional parameters
832                    shell_state.set_positional_params(old_positional);
833
834                    // Exit function context
835                    shell_state.exit_function();
836
837                    // Clear return state
838                    shell_state.clear_return();
839
840                    // Return the early return value
841                    return return_value;
842                }
843
844                // Restore old positional parameters
845                shell_state.set_positional_params(old_positional);
846
847                // Exit function context
848                shell_state.exit_function();
849
850                exit_code
851            } else {
852                eprintln!("Function '{}' not found", name);
853                1
854            }
855        }
856        Ast::Return { value } => {
857            // Return statements can only be used inside functions
858            if shell_state.function_depth == 0 {
859                eprintln!("Return statement outside of function");
860                return 1;
861            }
862
863            // Parse return value if provided
864            let exit_code = if let Some(ref val) = value {
865                val.parse::<i32>().unwrap_or(0)
866            } else {
867                0
868            };
869
870            // Set return state to indicate early return from function
871            shell_state.set_return(exit_code);
872
873            // Return the exit code - the function call handler will check for this
874            exit_code
875        }
876        Ast::And { left, right } => {
877            // Execute left side first
878            let left_exit = execute(*left, shell_state);
879
880            // Check if we got an early return from a function
881            if shell_state.is_returning() {
882                return left_exit;
883            }
884
885            // Only execute right side if left succeeded (exit code 0)
886            if left_exit == 0 {
887                execute(*right, shell_state)
888            } else {
889                left_exit
890            }
891        }
892        Ast::Or { left, right } => {
893            // Execute left side first
894            let left_exit = execute(*left, shell_state);
895
896            // Check if we got an early return from a function
897            if shell_state.is_returning() {
898                return left_exit;
899            }
900
901            // Only execute right side if left failed (exit code != 0)
902            if left_exit != 0 {
903                execute(*right, shell_state)
904            } else {
905                left_exit
906            }
907        }
908    }
909}
910
911fn execute_single_command(cmd: &ShellCommand, shell_state: &mut ShellState) -> i32 {
912    if cmd.args.is_empty() {
913        return 0;
914    }
915
916    // First expand variables, then wildcards
917    let var_expanded_args = expand_variables_in_args(&cmd.args, shell_state);
918    let expanded_args = match expand_wildcards(&var_expanded_args) {
919        Ok(args) => args,
920        Err(_) => return 1,
921    };
922
923    if expanded_args.is_empty() {
924        return 0;
925    }
926
927    // Check if this is a function call
928    if shell_state.get_function(&expanded_args[0]).is_some() {
929        // This is a function call - create a FunctionCall AST node and execute it
930        let function_call = Ast::FunctionCall {
931            name: expanded_args[0].clone(),
932            args: expanded_args[1..].to_vec(),
933        };
934        return execute(function_call, shell_state);
935    }
936
937    if crate::builtins::is_builtin(&expanded_args[0]) {
938        // Create a temporary ShellCommand with expanded args
939        let temp_cmd = ShellCommand {
940            args: expanded_args,
941            input: cmd.input.clone(),
942            output: cmd.output.clone(),
943            append: cmd.append.clone(),
944            here_doc_delimiter: None,
945            here_string_content: None,
946        };
947
948        // If we're capturing output, create a writer for it
949        if let Some(ref capture_buffer) = shell_state.capture_output.clone() {
950            // Create a writer that writes to our capture buffer
951            struct CaptureWriter {
952                buffer: Rc<RefCell<Vec<u8>>>,
953            }
954            impl std::io::Write for CaptureWriter {
955                fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
956                    self.buffer.borrow_mut().extend_from_slice(buf);
957                    Ok(buf.len())
958                }
959                fn flush(&mut self) -> std::io::Result<()> {
960                    Ok(())
961                }
962            }
963            let writer = CaptureWriter {
964                buffer: capture_buffer.clone(),
965            };
966            crate::builtins::execute_builtin(&temp_cmd, shell_state, Some(Box::new(writer)))
967        } else {
968            crate::builtins::execute_builtin(&temp_cmd, shell_state, None)
969        }
970    } else {
971        let mut command = Command::new(&expanded_args[0]);
972        command.args(&expanded_args[1..]);
973
974        // Set environment for child process
975        let child_env = shell_state.get_env_for_child();
976        command.env_clear();
977        for (key, value) in child_env {
978            command.env(key, value);
979        }
980
981        // If we're capturing output, redirect stdout to capture buffer
982        let capturing = shell_state.capture_output.is_some();
983        if capturing {
984            command.stdout(Stdio::piped());
985        }
986
987        // Handle input redirection
988        if let Some(ref input_file) = cmd.input {
989            let expanded_input = expand_variables_in_string(input_file, shell_state);
990            match File::open(&expanded_input) {
991                Ok(file) => {
992                    command.stdin(Stdio::from(file));
993                }
994                Err(e) => {
995                    if shell_state.colors_enabled {
996                        eprintln!(
997                            "{}Error opening input file '{}{}",
998                            shell_state.color_scheme.error,
999                            input_file,
1000                            &format!("': {}\x1b[0m", e)
1001                        );
1002                    } else {
1003                        eprintln!("Error opening input file '{}': {}", input_file, e);
1004                    }
1005                    return 1;
1006                }
1007            }
1008        } else if let Some(ref delimiter) = cmd.here_doc_delimiter {
1009            // Handle here-document redirection
1010            let here_doc_content = collect_here_document_content(delimiter, shell_state);
1011            let pipe_result = pipe();
1012            match pipe_result {
1013                Ok((reader, mut writer)) => {
1014                    use std::io::Write;
1015                    if let Err(e) = writeln!(writer, "{}", here_doc_content) {
1016                        if shell_state.colors_enabled {
1017                            eprintln!(
1018                                "{}Error writing here-document content: {}\x1b[0m",
1019                                shell_state.color_scheme.error, e
1020                            );
1021                        } else {
1022                            eprintln!("Error writing here-document content: {}", e);
1023                        }
1024                        return 1;
1025                    }
1026                    // Note: writer will be closed when it goes out of scope
1027                    command.stdin(Stdio::from(reader));
1028                }
1029                Err(e) => {
1030                    if shell_state.colors_enabled {
1031                        eprintln!(
1032                            "{}Error creating pipe for here-document: {}\x1b[0m",
1033                            shell_state.color_scheme.error, e
1034                        );
1035                    } else {
1036                        eprintln!("Error creating pipe for here-document: {}", e);
1037                    }
1038                    return 1;
1039                }
1040            }
1041        } else if let Some(ref content) = cmd.here_string_content {
1042            // Handle here-string redirection
1043            let expanded_content = expand_variables_in_string(content, shell_state);
1044            let pipe_result = pipe();
1045            match pipe_result {
1046                Ok((reader, mut writer)) => {
1047                    use std::io::Write;
1048                    if let Err(e) = write!(writer, "{}", expanded_content) {
1049                        if shell_state.colors_enabled {
1050                            eprintln!(
1051                                "{}Error writing here-string content: {}\x1b[0m",
1052                                shell_state.color_scheme.error, e
1053                            );
1054                        } else {
1055                            eprintln!("Error writing here-string content: {}", e);
1056                        }
1057                        return 1;
1058                    }
1059                    // Note: writer will be closed when it goes out of scope
1060                    command.stdin(Stdio::from(reader));
1061                }
1062                Err(e) => {
1063                    if shell_state.colors_enabled {
1064                        eprintln!(
1065                            "{}Error creating pipe for here-string: {}\x1b[0m",
1066                            shell_state.color_scheme.error, e
1067                        );
1068                    } else {
1069                        eprintln!("Error creating pipe for here-string: {}", e);
1070                    }
1071                    return 1;
1072                }
1073            }
1074        }
1075
1076        // Handle output redirection
1077        if let Some(ref output_file) = cmd.output {
1078            let expanded_output = expand_variables_in_string(output_file, shell_state);
1079            match File::create(&expanded_output) {
1080                Ok(file) => {
1081                    command.stdout(Stdio::from(file));
1082                }
1083                Err(e) => {
1084                    if shell_state.colors_enabled {
1085                        eprintln!(
1086                            "{}Error creating output file '{}{}",
1087                            shell_state.color_scheme.error,
1088                            output_file,
1089                            &format!("': {}\x1b[0m", e)
1090                        );
1091                    } else {
1092                        eprintln!("Error creating output file '{}': {}", output_file, e);
1093                    }
1094                    return 1;
1095                }
1096            }
1097        } else if let Some(ref append_file) = cmd.append {
1098            let expanded_append = expand_variables_in_string(append_file, shell_state);
1099            match File::options()
1100                .append(true)
1101                .create(true)
1102                .open(&expanded_append)
1103            {
1104                Ok(file) => {
1105                    command.stdout(Stdio::from(file));
1106                }
1107                Err(e) => {
1108                    if shell_state.colors_enabled {
1109                        eprintln!(
1110                            "{}Error opening append file '{}{}",
1111                            shell_state.color_scheme.error,
1112                            append_file,
1113                            &format!("': {}\x1b[0m", e)
1114                        );
1115                    } else {
1116                        eprintln!("Error opening append file '{}': {}", append_file, e);
1117                    }
1118                    return 1;
1119                }
1120            }
1121        }
1122
1123        match command.spawn() {
1124            Ok(mut child) => {
1125                // If capturing, read stdout
1126                if capturing && let Some(mut stdout) = child.stdout.take() {
1127                    use std::io::Read;
1128                    let mut output = Vec::new();
1129                    if stdout.read_to_end(&mut output).is_ok()
1130                        && let Some(ref capture_buffer) = shell_state.capture_output
1131                    {
1132                        capture_buffer.borrow_mut().extend_from_slice(&output);
1133                    }
1134                }
1135
1136                match child.wait() {
1137                    Ok(status) => status.code().unwrap_or(0),
1138                    Err(e) => {
1139                        if shell_state.colors_enabled {
1140                            eprintln!(
1141                                "{}Error waiting for command: {}\x1b[0m",
1142                                shell_state.color_scheme.error, e
1143                            );
1144                        } else {
1145                            eprintln!("Error waiting for command: {}", e);
1146                        }
1147                        1
1148                    }
1149                }
1150            }
1151            Err(e) => {
1152                if shell_state.colors_enabled {
1153                    eprintln!(
1154                        "{}Command spawn error: {}\x1b[0m",
1155                        shell_state.color_scheme.error, e
1156                    );
1157                } else {
1158                    eprintln!("Command spawn error: {}", e);
1159                }
1160                1
1161            }
1162        }
1163    }
1164}
1165
1166fn execute_pipeline(commands: &[ShellCommand], shell_state: &mut ShellState) -> i32 {
1167    let mut exit_code = 0;
1168    let mut previous_stdout = None;
1169
1170    for (i, cmd) in commands.iter().enumerate() {
1171        if cmd.args.is_empty() {
1172            continue;
1173        }
1174
1175        let is_last = i == commands.len() - 1;
1176
1177        // First expand variables, then wildcards
1178        let var_expanded_args = expand_variables_in_args(&cmd.args, shell_state);
1179        let expanded_args = match expand_wildcards(&var_expanded_args) {
1180            Ok(args) => args,
1181            Err(_) => return 1,
1182        };
1183
1184        if expanded_args.is_empty() {
1185            continue;
1186        }
1187
1188        if crate::builtins::is_builtin(&expanded_args[0]) {
1189            // Built-ins in pipelines are tricky - for now, execute them separately
1190            // This is not perfect but better than nothing
1191            let temp_cmd = ShellCommand {
1192                args: expanded_args,
1193                input: cmd.input.clone(),
1194                output: cmd.output.clone(),
1195                append: cmd.append.clone(),
1196                here_doc_delimiter: None,
1197                here_string_content: None,
1198            };
1199            if !is_last {
1200                // Create a safe pipe
1201                let (reader, writer) = match pipe() {
1202                    Ok(p) => p,
1203                    Err(e) => {
1204                        if shell_state.colors_enabled {
1205                            eprintln!(
1206                                "{}Error creating pipe for builtin: {}\x1b[0m",
1207                                shell_state.color_scheme.error, e
1208                            );
1209                        } else {
1210                            eprintln!("Error creating pipe for builtin: {}", e);
1211                        }
1212                        return 1;
1213                    }
1214                };
1215                // Execute builtin with writer for output capture
1216                exit_code = crate::builtins::execute_builtin(
1217                    &temp_cmd,
1218                    shell_state,
1219                    Some(Box::new(writer)),
1220                );
1221                // Use reader for next command's stdin
1222                previous_stdout = Some(Stdio::from(reader));
1223            } else {
1224                // Last command: no need to pipe output
1225                exit_code = crate::builtins::execute_builtin(&temp_cmd, shell_state, None);
1226                previous_stdout = None;
1227            }
1228        } else {
1229            let mut command = Command::new(&expanded_args[0]);
1230            command.args(&expanded_args[1..]);
1231
1232            // Set environment for child process
1233            let child_env = shell_state.get_env_for_child();
1234            command.env_clear();
1235            for (key, value) in child_env {
1236                command.env(key, value);
1237            }
1238
1239            // Set stdin from previous command's stdout
1240            if let Some(prev) = previous_stdout.take() {
1241                command.stdin(prev);
1242            }
1243
1244            // Set stdout for next command, unless this is the last
1245            if !is_last {
1246                command.stdout(Stdio::piped());
1247            }
1248
1249            // Handle input redirection (only for first command)
1250            if i == 0 {
1251                if let Some(ref input_file) = cmd.input {
1252                    let expanded_input = expand_variables_in_string(input_file, shell_state);
1253                    match File::open(&expanded_input) {
1254                        Ok(file) => {
1255                            command.stdin(Stdio::from(file));
1256                        }
1257                        Err(e) => {
1258                            if shell_state.colors_enabled {
1259                                eprintln!(
1260                                    "{}Error opening input file '{}{}",
1261                                    shell_state.color_scheme.error,
1262                                    input_file,
1263                                    &format!("': {}\x1b[0m", e)
1264                                );
1265                            } else {
1266                                eprintln!("Error opening input file '{}': {}", input_file, e);
1267                            }
1268                            return 1;
1269                        }
1270                    }
1271                } else if let Some(ref delimiter) = cmd.here_doc_delimiter {
1272                    // Handle here-document redirection for first command in pipeline
1273                    let here_doc_content = collect_here_document_content(delimiter, shell_state);
1274                    let pipe_result = pipe();
1275                    match pipe_result {
1276                        Ok((reader, mut writer)) => {
1277                            use std::io::Write;
1278                            if let Err(e) = writeln!(writer, "{}", here_doc_content) {
1279                                if shell_state.colors_enabled {
1280                                    eprintln!(
1281                                        "{}Error writing here-document content: {}\x1b[0m",
1282                                        shell_state.color_scheme.error, e
1283                                    );
1284                                } else {
1285                                    eprintln!("Error writing here-document content: {}", e);
1286                                }
1287                                return 1;
1288                            }
1289                            command.stdin(Stdio::from(reader));
1290                        }
1291                        Err(e) => {
1292                            if shell_state.colors_enabled {
1293                                eprintln!(
1294                                    "{}Error creating pipe for here-document: {}\x1b[0m",
1295                                    shell_state.color_scheme.error, e
1296                                );
1297                            } else {
1298                                eprintln!("Error creating pipe for here-document: {}", e);
1299                            }
1300                            return 1;
1301                        }
1302                    }
1303                } else if let Some(ref content) = cmd.here_string_content {
1304                    // Handle here-string redirection for first command in pipeline
1305                    let expanded_content = expand_variables_in_string(content, shell_state);
1306                    let pipe_result = pipe();
1307                    match pipe_result {
1308                        Ok((reader, mut writer)) => {
1309                            use std::io::Write;
1310                            if let Err(e) = write!(writer, "{}", expanded_content) {
1311                                if shell_state.colors_enabled {
1312                                    eprintln!(
1313                                        "{}Error writing here-string content: {}\x1b[0m",
1314                                        shell_state.color_scheme.error, e
1315                                    );
1316                                } else {
1317                                    eprintln!("Error writing here-string content: {}", e);
1318                                }
1319                                return 1;
1320                            }
1321                            command.stdin(Stdio::from(reader));
1322                        }
1323                        Err(e) => {
1324                            if shell_state.colors_enabled {
1325                                eprintln!(
1326                                    "{}Error creating pipe for here-string: {}\x1b[0m",
1327                                    shell_state.color_scheme.error, e
1328                                );
1329                            } else {
1330                                eprintln!("Error creating pipe for here-string: {}", e);
1331                            }
1332                            return 1;
1333                        }
1334                    }
1335                }
1336            }
1337
1338            // Handle output redirection (only for last command)
1339            if is_last {
1340                if let Some(ref output_file) = cmd.output {
1341                    let expanded_output = expand_variables_in_string(output_file, shell_state);
1342                    match File::create(&expanded_output) {
1343                        Ok(file) => {
1344                            command.stdout(Stdio::from(file));
1345                        }
1346                        Err(e) => {
1347                            if shell_state.colors_enabled {
1348                                eprintln!(
1349                                    "{}Error creating output file '{}{}",
1350                                    shell_state.color_scheme.error,
1351                                    output_file,
1352                                    &format!("': {}\x1b[0m", e)
1353                                );
1354                            } else {
1355                                eprintln!("Error creating output file '{}': {}", output_file, e);
1356                            }
1357                            return 1;
1358                        }
1359                    }
1360                } else if let Some(ref append_file) = cmd.append {
1361                    let expanded_append = expand_variables_in_string(append_file, shell_state);
1362                    match File::options()
1363                        .append(true)
1364                        .create(true)
1365                        .open(&expanded_append)
1366                    {
1367                        Ok(file) => {
1368                            command.stdout(Stdio::from(file));
1369                        }
1370                        Err(e) => {
1371                            if shell_state.colors_enabled {
1372                                eprintln!(
1373                                    "{}Error opening append file '{}{}",
1374                                    shell_state.color_scheme.error,
1375                                    append_file,
1376                                    &format!("': {}\x1b[0m", e)
1377                                );
1378                            } else {
1379                                eprintln!("Error opening append file '{}': {}", append_file, e);
1380                            }
1381                            return 1;
1382                        }
1383                    }
1384                }
1385            }
1386
1387            match command.spawn() {
1388                Ok(mut child) => {
1389                    if !is_last {
1390                        previous_stdout = child.stdout.take().map(Stdio::from);
1391                    }
1392                    match child.wait() {
1393                        Ok(status) => {
1394                            exit_code = status.code().unwrap_or(0);
1395                        }
1396                        Err(e) => {
1397                            if shell_state.colors_enabled {
1398                                eprintln!(
1399                                    "{}Error waiting for command: {}\x1b[0m",
1400                                    shell_state.color_scheme.error, e
1401                                );
1402                            } else {
1403                                eprintln!("Error waiting for command: {}", e);
1404                            }
1405                            exit_code = 1;
1406                        }
1407                    }
1408                }
1409                Err(e) => {
1410                    if shell_state.colors_enabled {
1411                        eprintln!(
1412                            "{}Error spawning command '{}{}",
1413                            shell_state.color_scheme.error,
1414                            expanded_args[0],
1415                            &format!("': {}\x1b[0m", e)
1416                        );
1417                    } else {
1418                        eprintln!("Error spawning command '{}': {}", expanded_args[0], e);
1419                    }
1420                    exit_code = 1;
1421                }
1422            }
1423        }
1424    }
1425
1426    exit_code
1427}
1428
1429#[cfg(test)]
1430mod tests {
1431    use super::*;
1432
1433    #[test]
1434    fn test_execute_single_command_builtin() {
1435        let cmd = ShellCommand {
1436            args: vec!["true".to_string()],
1437            input: None,
1438            output: None,
1439            append: None,
1440            here_doc_delimiter: None,
1441            here_string_content: None,
1442        };
1443        let mut shell_state = ShellState::new();
1444        let exit_code = execute_single_command(&cmd, &mut shell_state);
1445        assert_eq!(exit_code, 0);
1446    }
1447
1448    // For external commands, test with a command that exists
1449    #[test]
1450    fn test_execute_single_command_external() {
1451        let cmd = ShellCommand {
1452            args: vec!["true".to_string()], // Assume true exists
1453            input: None,
1454            output: None,
1455            append: None,
1456            here_doc_delimiter: None,
1457            here_string_content: None,
1458        };
1459        let mut shell_state = ShellState::new();
1460        let exit_code = execute_single_command(&cmd, &mut shell_state);
1461        assert_eq!(exit_code, 0);
1462    }
1463
1464    #[test]
1465    fn test_execute_single_command_external_nonexistent() {
1466        let cmd = ShellCommand {
1467            args: vec!["nonexistent_command".to_string()],
1468            input: None,
1469            output: None,
1470            append: None,
1471            here_doc_delimiter: None,
1472            here_string_content: None,
1473        };
1474        let mut shell_state = ShellState::new();
1475        let exit_code = execute_single_command(&cmd, &mut shell_state);
1476        assert_eq!(exit_code, 1); // Command not found
1477    }
1478
1479    #[test]
1480    fn test_execute_pipeline() {
1481        let commands = vec![
1482            ShellCommand {
1483                args: vec!["printf".to_string(), "hello".to_string()],
1484                input: None,
1485                output: None,
1486                append: None,
1487                here_doc_delimiter: None,
1488                here_string_content: None,
1489            },
1490            ShellCommand {
1491                args: vec!["cat".to_string()], // cat reads from stdin
1492                input: None,
1493                output: None,
1494                append: None,
1495                here_doc_delimiter: None,
1496                here_string_content: None,
1497            },
1498        ];
1499        let mut shell_state = ShellState::new();
1500        let exit_code = execute_pipeline(&commands, &mut shell_state);
1501        assert_eq!(exit_code, 0);
1502    }
1503
1504    #[test]
1505    fn test_execute_empty_pipeline() {
1506        let commands = vec![];
1507        let mut shell_state = ShellState::new();
1508        let exit_code = execute(Ast::Pipeline(commands), &mut shell_state);
1509        assert_eq!(exit_code, 0);
1510    }
1511
1512    #[test]
1513    fn test_execute_single_command() {
1514        let ast = Ast::Pipeline(vec![ShellCommand {
1515            args: vec!["true".to_string()],
1516            input: None,
1517            output: None,
1518            append: None,
1519            here_doc_delimiter: None,
1520            here_string_content: None,
1521        }]);
1522        let mut shell_state = ShellState::new();
1523        let exit_code = execute(ast, &mut shell_state);
1524        assert_eq!(exit_code, 0);
1525    }
1526
1527    #[test]
1528    fn test_execute_function_definition() {
1529        let ast = Ast::FunctionDefinition {
1530            name: "test_func".to_string(),
1531            body: Box::new(Ast::Pipeline(vec![ShellCommand {
1532                args: vec!["echo".to_string(), "hello".to_string()],
1533                input: None,
1534                output: None,
1535                append: None,
1536                here_doc_delimiter: None,
1537                here_string_content: None,
1538            }])),
1539        };
1540        let mut shell_state = ShellState::new();
1541        let exit_code = execute(ast, &mut shell_state);
1542        assert_eq!(exit_code, 0);
1543
1544        // Check that function was stored
1545        assert!(shell_state.get_function("test_func").is_some());
1546    }
1547
1548    #[test]
1549    fn test_execute_function_call() {
1550        // First define a function
1551        let mut shell_state = ShellState::new();
1552        shell_state.define_function(
1553            "test_func".to_string(),
1554            Ast::Pipeline(vec![ShellCommand {
1555                args: vec!["echo".to_string(), "hello".to_string()],
1556                input: None,
1557                output: None,
1558                append: None,
1559                here_doc_delimiter: None,
1560                here_string_content: None,
1561            }]),
1562        );
1563
1564        // Now call the function
1565        let ast = Ast::FunctionCall {
1566            name: "test_func".to_string(),
1567            args: vec![],
1568        };
1569        let exit_code = execute(ast, &mut shell_state);
1570        assert_eq!(exit_code, 0);
1571    }
1572
1573    #[test]
1574    fn test_execute_function_call_with_args() {
1575        // First define a function that uses arguments
1576        let mut shell_state = ShellState::new();
1577        shell_state.define_function(
1578            "test_func".to_string(),
1579            Ast::Pipeline(vec![ShellCommand {
1580                args: vec!["echo".to_string(), "arg1".to_string()],
1581                input: None,
1582                output: None,
1583                append: None,
1584                here_doc_delimiter: None,
1585                here_string_content: None,
1586            }]),
1587        );
1588
1589        // Now call the function with arguments
1590        let ast = Ast::FunctionCall {
1591            name: "test_func".to_string(),
1592            args: vec!["hello".to_string()],
1593        };
1594        let exit_code = execute(ast, &mut shell_state);
1595        assert_eq!(exit_code, 0);
1596    }
1597
1598    #[test]
1599    fn test_execute_nonexistent_function() {
1600        let mut shell_state = ShellState::new();
1601        let ast = Ast::FunctionCall {
1602            name: "nonexistent".to_string(),
1603            args: vec![],
1604        };
1605        let exit_code = execute(ast, &mut shell_state);
1606        assert_eq!(exit_code, 1); // Should return error code
1607    }
1608
1609    #[test]
1610    fn test_execute_function_integration() {
1611        // Test full integration: define function, then call it
1612        let mut shell_state = ShellState::new();
1613
1614        // First define a function
1615        let define_ast = Ast::FunctionDefinition {
1616            name: "hello".to_string(),
1617            body: Box::new(Ast::Pipeline(vec![ShellCommand {
1618                args: vec!["printf".to_string(), "Hello from function".to_string()],
1619                input: None,
1620                output: None,
1621                append: None,
1622                here_doc_delimiter: None,
1623                here_string_content: None,
1624            }])),
1625        };
1626        let exit_code = execute(define_ast, &mut shell_state);
1627        assert_eq!(exit_code, 0);
1628
1629        // Now call the function
1630        let call_ast = Ast::FunctionCall {
1631            name: "hello".to_string(),
1632            args: vec![],
1633        };
1634        let exit_code = execute(call_ast, &mut shell_state);
1635        assert_eq!(exit_code, 0);
1636    }
1637
1638    #[test]
1639    fn test_execute_function_with_local_variables() {
1640        let mut shell_state = ShellState::new();
1641
1642        // Set a global variable
1643        shell_state.set_var("global_var", "global_value".to_string());
1644
1645        // Define a function that uses local variables
1646        let define_ast = Ast::FunctionDefinition {
1647            name: "test_func".to_string(),
1648            body: Box::new(Ast::Sequence(vec![
1649                Ast::LocalAssignment {
1650                    var: "local_var".to_string(),
1651                    value: "local_value".to_string(),
1652                },
1653                Ast::Assignment {
1654                    var: "global_var".to_string(),
1655                    value: "modified_in_function".to_string(),
1656                },
1657                Ast::Pipeline(vec![ShellCommand {
1658                    args: vec!["printf".to_string(), "success".to_string()],
1659                    input: None,
1660                    output: None,
1661                    append: None,
1662                    here_doc_delimiter: None,
1663                    here_string_content: None,
1664                }]),
1665            ])),
1666        };
1667        let exit_code = execute(define_ast, &mut shell_state);
1668        assert_eq!(exit_code, 0);
1669
1670        // Global variable should not be modified during function definition
1671        assert_eq!(
1672            shell_state.get_var("global_var"),
1673            Some("global_value".to_string())
1674        );
1675
1676        // Call the function
1677        let call_ast = Ast::FunctionCall {
1678            name: "test_func".to_string(),
1679            args: vec![],
1680        };
1681        let exit_code = execute(call_ast, &mut shell_state);
1682        assert_eq!(exit_code, 0);
1683
1684        // After function call, global variable should be modified since function assignments affect global scope
1685        assert_eq!(
1686            shell_state.get_var("global_var"),
1687            Some("modified_in_function".to_string())
1688        );
1689    }
1690
1691    #[test]
1692    fn test_execute_nested_function_calls() {
1693        let mut shell_state = ShellState::new();
1694
1695        // Set global variable
1696        shell_state.set_var("global_var", "global".to_string());
1697
1698        // Define outer function
1699        let outer_func = Ast::FunctionDefinition {
1700            name: "outer".to_string(),
1701            body: Box::new(Ast::Sequence(vec![
1702                Ast::Assignment {
1703                    var: "global_var".to_string(),
1704                    value: "outer_modified".to_string(),
1705                },
1706                Ast::FunctionCall {
1707                    name: "inner".to_string(),
1708                    args: vec![],
1709                },
1710                Ast::Pipeline(vec![ShellCommand {
1711                    args: vec!["printf".to_string(), "outer_done".to_string()],
1712                    input: None,
1713                    output: None,
1714                    append: None,
1715                    here_doc_delimiter: None,
1716                    here_string_content: None,
1717                }]),
1718            ])),
1719        };
1720
1721        // Define inner function
1722        let inner_func = Ast::FunctionDefinition {
1723            name: "inner".to_string(),
1724            body: Box::new(Ast::Sequence(vec![
1725                Ast::Assignment {
1726                    var: "global_var".to_string(),
1727                    value: "inner_modified".to_string(),
1728                },
1729                Ast::Pipeline(vec![ShellCommand {
1730                    args: vec!["printf".to_string(), "inner_done".to_string()],
1731                    input: None,
1732                    output: None,
1733                    append: None,
1734                    here_doc_delimiter: None,
1735                    here_string_content: None,
1736                }]),
1737            ])),
1738        };
1739
1740        // Define both functions
1741        execute(outer_func, &mut shell_state);
1742        execute(inner_func, &mut shell_state);
1743
1744        // Set initial global value
1745        shell_state.set_var("global_var", "initial".to_string());
1746
1747        // Call outer function (which calls inner function)
1748        let call_ast = Ast::FunctionCall {
1749            name: "outer".to_string(),
1750            args: vec![],
1751        };
1752        let exit_code = execute(call_ast, &mut shell_state);
1753        assert_eq!(exit_code, 0);
1754
1755        // After nested function calls, global variable should be modified by inner function
1756        // (bash behavior: function variable assignments affect global scope)
1757        assert_eq!(
1758            shell_state.get_var("global_var"),
1759            Some("inner_modified".to_string())
1760        );
1761    }
1762
1763    #[test]
1764    fn test_here_string_execution() {
1765        // Test here-string redirection with a simple command
1766        let cmd = ShellCommand {
1767            args: vec!["cat".to_string()],
1768            input: None,
1769            output: None,
1770            append: None,
1771            here_doc_delimiter: None,
1772            here_string_content: Some("hello world".to_string()),
1773        };
1774
1775        // Note: This test would require mocking stdin to provide the here-string content
1776        // For now, we'll just verify the command structure is parsed correctly
1777        assert_eq!(cmd.args, vec!["cat"]);
1778        assert_eq!(cmd.here_string_content, Some("hello world".to_string()));
1779    }
1780
1781    #[test]
1782    fn test_here_document_execution() {
1783        // Test here-document redirection with a simple command
1784        let cmd = ShellCommand {
1785            args: vec!["cat".to_string()],
1786            input: None,
1787            output: None,
1788            append: None,
1789            here_doc_delimiter: Some("EOF".to_string()),
1790            here_string_content: None,
1791        };
1792
1793        // Note: This test would require mocking stdin to provide the here-document content
1794        // For now, we'll just verify the command structure is parsed correctly
1795        assert_eq!(cmd.args, vec!["cat"]);
1796        assert_eq!(cmd.here_doc_delimiter, Some("EOF".to_string()));
1797    }
1798}