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