rush_sh/
executor.rs

1use std::fs::File;
2use std::io::pipe;
3use std::process::{Command, Stdio};
4use std::rc::Rc;
5use std::cell::RefCell;
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                };
72                
73                // Execute builtin with our writer
74                let exit_code = crate::builtins::execute_builtin(
75                    &temp_cmd,
76                    shell_state,
77                    Some(Box::new(writer)),
78                );
79                
80                // Read the captured output
81                drop(temp_cmd); // Ensure writer is dropped
82                let mut output = String::new();
83                use std::io::Read;
84                let mut reader = reader;
85                reader.read_to_string(&mut output)
86                    .map_err(|e| format!("Failed to read output: {}", e))?;
87                
88                if exit_code == 0 {
89                    Ok(output.trim_end().to_string())
90                } else {
91                    Err(format!("Command failed with exit code {}", exit_code))
92                }
93            } else {
94                // External command - execute with output capture
95                drop(writer); // Close writer end before spawning
96                
97                let mut command = Command::new(&expanded_args[0]);
98                command.args(&expanded_args[1..]);
99                command.stdout(Stdio::piped());
100                command.stderr(Stdio::null()); // Suppress stderr for command substitution
101                
102                // Set environment
103                let child_env = shell_state.get_env_for_child();
104                command.env_clear();
105                for (key, value) in child_env {
106                    command.env(key, value);
107                }
108                
109                let output = command.output()
110                    .map_err(|e| format!("Failed to execute command: {}", e))?;
111                
112                if output.status.success() {
113                    Ok(String::from_utf8_lossy(&output.stdout).trim_end().to_string())
114                } else {
115                    Err(format!("Command failed with exit code {}", output.status.code().unwrap_or(1)))
116                }
117            }
118        }
119        _ => {
120            // For complex AST nodes (sequences, pipelines, etc.), we need to execute them
121            // and capture output differently. For now, fall back to executing and capturing
122            // via a temporary approach
123            drop(writer);
124            
125            // Execute the AST normally but we can't easily capture output for complex cases
126            // This is a limitation we'll need to address for full support
127            Err("Complex command substitutions not yet fully supported".to_string())
128        }
129    }
130}
131
132fn expand_variables_in_args(args: &[String], shell_state: &mut ShellState) -> Vec<String> {
133    let mut expanded_args = Vec::new();
134
135    for arg in args {
136        // Expand variables within the argument string
137        let expanded_arg = expand_variables_in_string(arg, shell_state);
138        expanded_args.push(expanded_arg);
139    }
140
141    expanded_args
142}
143
144pub fn expand_variables_in_string(input: &str, shell_state: &mut ShellState) -> String {
145    let mut result = String::new();
146    let mut chars = input.chars().peekable();
147
148    while let Some(ch) = chars.next() {
149        if ch == '$' {
150            // Check for command substitution $(...) or arithmetic expansion $((...))
151            if let Some(&'(') = chars.peek() {
152                chars.next(); // consume first (
153                
154                // Check if this is arithmetic expansion $((...))
155                if let Some(&'(') = chars.peek() {
156                    // Arithmetic expansion $((...))
157                    chars.next(); // consume second (
158                    let mut arithmetic_expr = String::new();
159                    let mut paren_depth = 1;
160                    let mut found_closing = false;
161                    
162                    while let Some(c) = chars.next() {
163                        if c == '(' {
164                            paren_depth += 1;
165                            arithmetic_expr.push(c);
166                        } else if c == ')' {
167                            paren_depth -= 1;
168                            if paren_depth == 0 {
169                                // Found the first closing ) - check for second )
170                                if let Some(&')') = chars.peek() {
171                                    chars.next(); // consume the second )
172                                    found_closing = true;
173                                    break;
174                                } else {
175                                    // Missing second closing paren, treat as error
176                                    result.push_str("$((");
177                                    result.push_str(&arithmetic_expr);
178                                    result.push(')');
179                                    break;
180                                }
181                            }
182                            arithmetic_expr.push(c);
183                        } else {
184                            arithmetic_expr.push(c);
185                        }
186                    }
187                    
188                    if found_closing {
189                        // First expand variables in the arithmetic expression
190                        // The arithmetic evaluator expects variable names without $ prefix
191                        // So we need to expand $VAR to the value before evaluation
192                        let mut expanded_expr = String::new();
193                        let mut expr_chars = arithmetic_expr.chars().peekable();
194                        
195                        while let Some(ch) = expr_chars.next() {
196                            if ch == '$' {
197                                // Expand variable
198                                let mut var_name = String::new();
199                                if let Some(&c) = expr_chars.peek() {
200                                    if c == '?' || c == '$' || c == '0' || c == '#' || c == '*' || c == '@' || c.is_ascii_digit() {
201                                        var_name.push(c);
202                                        expr_chars.next();
203                                    } else {
204                                        while let Some(&c) = expr_chars.peek() {
205                                            if c.is_alphanumeric() || c == '_' {
206                                                var_name.push(c);
207                                                expr_chars.next();
208                                            } else {
209                                                break;
210                                            }
211                                        }
212                                    }
213                                }
214                                
215                                if !var_name.is_empty() {
216                                    if let Some(value) = shell_state.get_var(&var_name) {
217                                        expanded_expr.push_str(&value);
218                                    } else {
219                                        // Variable not found, use 0 for arithmetic
220                                        expanded_expr.push('0');
221                                    }
222                                } else {
223                                    expanded_expr.push('$');
224                                }
225                            } else {
226                                expanded_expr.push(ch);
227                            }
228                        }
229                        
230                        match crate::arithmetic::evaluate_arithmetic_expression(&expanded_expr, shell_state) {
231                            Ok(value) => {
232                                result.push_str(&value.to_string());
233                            }
234                            Err(e) => {
235                                // On arithmetic error, display a proper error message
236                                if shell_state.colors_enabled {
237                                    result.push_str(&format!("{}arithmetic error: {}{}",
238                                        shell_state.color_scheme.error, e, "\x1b[0m"));
239                                } else {
240                                    result.push_str(&format!("arithmetic error: {}", e));
241                                }
242                            }
243                        }
244                    } else {
245                        // Didn't find proper closing - keep as literal
246                        result.push_str("$((");
247                        result.push_str(&arithmetic_expr);
248                        // Note: we don't add closing parens since they weren't in the input
249                    }
250                    continue;
251                }
252                
253                // Regular command substitution $(...)
254                let mut sub_command = String::new();
255                let mut paren_depth = 1;
256                
257                for c in chars.by_ref() {
258                    if c == '(' {
259                        paren_depth += 1;
260                        sub_command.push(c);
261                    } else if c == ')' {
262                        paren_depth -= 1;
263                        if paren_depth == 0 {
264                            break;
265                        }
266                        sub_command.push(c);
267                    } else {
268                        sub_command.push(c);
269                    }
270                }
271                
272                // Execute the command substitution within the current shell context
273                // Parse and execute the command using our own lexer/parser/executor
274                if let Ok(tokens) = crate::lexer::lex(&sub_command, shell_state) {
275                    // Expand aliases before parsing
276                    let expanded_tokens = match crate::lexer::expand_aliases(
277                        tokens,
278                        shell_state,
279                        &mut std::collections::HashSet::new()
280                    ) {
281                        Ok(t) => t,
282                        Err(_) => {
283                            // Alias expansion error, keep literal
284                            result.push_str("$(");
285                            result.push_str(&sub_command);
286                            result.push(')');
287                            continue;
288                        }
289                    };
290                    
291                    if let Ok(ast) = crate::parser::parse(expanded_tokens) {
292                        // Execute within current shell context and capture output
293                        match execute_and_capture_output(ast, shell_state) {
294                            Ok(output) => {
295                                result.push_str(&output);
296                            }
297                            Err(_) => {
298                                // On failure, keep the literal
299                                result.push_str("$(");
300                                result.push_str(&sub_command);
301                                result.push(')');
302                            }
303                        }
304                    } else {
305                        // Parse error - try to handle as function call if it looks like one
306                        let tokens_str = sub_command.trim();
307                        if tokens_str.contains(' ') {
308                            // Split by spaces and check if first token looks like a function call
309                            let parts: Vec<&str> = tokens_str.split_whitespace().collect();
310                            if let Some(first_token) = parts.first()
311                                && shell_state.get_function(first_token).is_some() {
312                                    // This is a function call, create AST manually
313                                    let function_call = Ast::FunctionCall {
314                                        name: first_token.to_string(),
315                                        args: parts[1..].iter().map(|s| s.to_string()).collect(),
316                                    };
317                                    match execute_and_capture_output(function_call, shell_state) {
318                                        Ok(output) => {
319                                            result.push_str(&output);
320                                            continue;
321                                        }
322                                        Err(_) => {
323                                            // Fall back to literal
324                                        }
325                                    }
326                                }
327                        }
328                        // Keep the literal
329                        result.push_str("$(");
330                        result.push_str(&sub_command);
331                        result.push(')');
332                    }
333                } else {
334                    // Lex error, keep literal
335                    result.push_str("$(");
336                    result.push_str(&sub_command);
337                    result.push(')');
338                }
339            } else {
340                // Regular variable
341                let mut var_name = String::new();
342                let mut next_ch = chars.peek();
343
344                // Handle special single-character variables first
345                if let Some(&c) = next_ch {
346                    if c == '?' || c == '$' || c == '0' || c == '#' || c == '*' || c == '@' {
347                        var_name.push(c);
348                        chars.next(); // consume the character
349                    } else if c.is_ascii_digit() {
350                        // Positional parameter
351                        var_name.push(c);
352                        chars.next();
353                    } else {
354                        // Regular variable name
355                        while let Some(&c) = next_ch {
356                            if c.is_alphanumeric() || c == '_' {
357                                var_name.push(c);
358                                chars.next(); // consume the character
359                                next_ch = chars.peek();
360                            } else {
361                                break;
362                            }
363                        }
364                    }
365                }
366
367                if !var_name.is_empty() {
368                    if let Some(value) = shell_state.get_var(&var_name) {
369                        result.push_str(&value);
370                    } else {
371                        // Variable not found - for positional parameters, expand to empty string
372                        // For other variables, keep the literal
373                        if var_name.chars().next().unwrap().is_ascii_digit() ||
374                           var_name == "?" || var_name == "$" || var_name == "0" ||
375                           var_name == "#" || var_name == "*" || var_name == "@" {
376                            // Expand to empty string for undefined positional parameters
377                        } else {
378                            // Keep the literal for regular variables
379                            result.push('$');
380                            result.push_str(&var_name);
381                        }
382                    }
383                } else {
384                    result.push('$');
385                }
386            }
387        } else if ch == '`' {
388            // Backtick command substitution
389            let mut sub_command = String::new();
390            
391            for c in chars.by_ref() {
392                if c == '`' {
393                    break;
394                }
395                sub_command.push(c);
396            }
397            
398            // Execute the command substitution
399            if let Ok(tokens) = crate::lexer::lex(&sub_command, shell_state) {
400                // Expand aliases before parsing
401                let expanded_tokens = match crate::lexer::expand_aliases(
402                    tokens,
403                    shell_state,
404                    &mut std::collections::HashSet::new()
405                ) {
406                    Ok(t) => t,
407                    Err(_) => {
408                        // Alias expansion error, keep literal
409                        result.push('`');
410                        result.push_str(&sub_command);
411                        result.push('`');
412                        continue;
413                    }
414                };
415                
416                if let Ok(ast) = crate::parser::parse(expanded_tokens) {
417                    // Execute and capture output
418                    match execute_and_capture_output(ast, shell_state) {
419                        Ok(output) => {
420                            result.push_str(&output);
421                        }
422                        Err(_) => {
423                            // On failure, keep the literal
424                            result.push('`');
425                            result.push_str(&sub_command);
426                            result.push('`');
427                        }
428                    }
429                } else {
430                    // Parse error, keep literal
431                    result.push('`');
432                    result.push_str(&sub_command);
433                    result.push('`');
434                }
435            } else {
436                // Lex error, keep literal
437                result.push('`');
438                result.push_str(&sub_command);
439                result.push('`');
440            }
441        } else {
442            result.push(ch);
443        }
444    }
445
446    result
447}
448
449fn expand_wildcards(args: &[String]) -> Result<Vec<String>, String> {
450    let mut expanded_args = Vec::new();
451
452    for arg in args {
453        if arg.contains('*') || arg.contains('?') || arg.contains('[') {
454            // Try to expand wildcard
455            match glob::glob(arg) {
456                Ok(paths) => {
457                    let mut matches: Vec<String> = paths
458                        .filter_map(|p| p.ok())
459                        .map(|p| p.to_string_lossy().to_string())
460                        .collect();
461                    if matches.is_empty() {
462                        // No matches, keep literal
463                        expanded_args.push(arg.clone());
464                    } else {
465                        // Sort for consistent behavior
466                        matches.sort();
467                        expanded_args.extend(matches);
468                    }
469                }
470                Err(_e) => {
471                    // Invalid pattern, keep literal
472                    expanded_args.push(arg.clone());
473                }
474            }
475        } else {
476            expanded_args.push(arg.clone());
477        }
478    }
479    Ok(expanded_args)
480}
481
482pub fn execute(ast: Ast, shell_state: &mut ShellState) -> i32 {
483    match ast {
484        Ast::Assignment { var, value } => {
485            // Expand variables and command substitutions in the value
486            let expanded_value = expand_variables_in_string(&value, shell_state);
487            shell_state.set_var(&var, expanded_value);
488            0
489        }
490        Ast::LocalAssignment { var, value } => {
491            // Expand variables and command substitutions in the value
492            let expanded_value = expand_variables_in_string(&value, shell_state);
493            shell_state.set_local_var(&var, expanded_value);
494            0
495        }
496        Ast::Pipeline(commands) => {
497            if commands.is_empty() {
498                return 0;
499            }
500
501            if commands.len() == 1 {
502                // Single command, handle redirections
503                execute_single_command(&commands[0], shell_state)
504            } else {
505                // Pipeline
506                execute_pipeline(&commands, shell_state)
507            }
508        }
509        Ast::Sequence(asts) => {
510            let mut exit_code = 0;
511            for ast in asts {
512                exit_code = execute(ast, shell_state);
513
514                // Check if we got an early return from a function
515                if shell_state.is_returning() {
516                    return exit_code;
517                }
518            }
519            exit_code
520        }
521        Ast::If {
522            branches,
523            else_branch,
524        } => {
525            for (condition, then_branch) in branches {
526                let cond_exit = execute(*condition, shell_state);
527                if cond_exit == 0 {
528                    let exit_code = execute(*then_branch, shell_state);
529
530                    // Check if we got an early return from a function
531                    if shell_state.is_returning() {
532                        return exit_code;
533                    }
534
535                    return exit_code;
536                }
537            }
538            if let Some(else_b) = else_branch {
539                let exit_code = execute(*else_b, shell_state);
540
541                // Check if we got an early return from a function
542                if shell_state.is_returning() {
543                    return exit_code;
544                }
545
546                exit_code
547            } else {
548                0
549            }
550        }
551        Ast::Case {
552            word,
553            cases,
554            default,
555        } => {
556            for (patterns, branch) in cases {
557                for pattern in &patterns {
558                    if let Ok(glob_pattern) = glob::Pattern::new(pattern) {
559                        if glob_pattern.matches(&word) {
560                            let exit_code = execute(branch, shell_state);
561
562                            // Check if we got an early return from a function
563                            if shell_state.is_returning() {
564                                return exit_code;
565                            }
566
567                            return exit_code;
568                        }
569                    } else {
570                        // If pattern is invalid, fall back to exact match
571                        if &word == pattern {
572                            let exit_code = execute(branch, shell_state);
573
574                            // Check if we got an early return from a function
575                            if shell_state.is_returning() {
576                                return exit_code;
577                            }
578
579                            return exit_code;
580                        }
581                    }
582                }
583            }
584            if let Some(def) = default {
585                let exit_code = execute(*def, shell_state);
586
587                // Check if we got an early return from a function
588                if shell_state.is_returning() {
589                    return exit_code;
590                }
591
592                exit_code
593            } else {
594                0
595            }
596        }
597        Ast::For { variable, items, body } => {
598            let mut exit_code = 0;
599            
600            // Execute the loop body for each item
601            for item in items {
602                // Set the loop variable
603                shell_state.set_var(&variable, item.clone());
604                
605                // Execute the body
606                exit_code = execute(*body.clone(), shell_state);
607                
608                // Check if we got an early return from a function
609                if shell_state.is_returning() {
610                    return exit_code;
611                }
612            }
613            
614            exit_code
615        }
616        Ast::While { condition, body } => {
617            let mut exit_code = 0;
618            
619            // Execute the loop while condition is true (exit code 0)
620            loop {
621                // Evaluate the condition
622                let cond_exit = execute(*condition.clone(), shell_state);
623                
624                // Check if we got an early return from a function
625                if shell_state.is_returning() {
626                    return cond_exit;
627                }
628                
629                // If condition is false (non-zero exit code), break
630                if cond_exit != 0 {
631                    break;
632                }
633                
634                // Execute the body
635                exit_code = execute(*body.clone(), 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            
643            exit_code
644        }
645        Ast::FunctionDefinition { name, body } => {
646            // Store function definition in shell state
647            shell_state.define_function(name.clone(), *body);
648            0
649        }
650        Ast::FunctionCall { name, args } => {
651            if let Some(function_body) = shell_state.get_function(&name).cloned() {
652                // Check recursion limit before entering function
653                if shell_state.function_depth >= shell_state.max_recursion_depth {
654                    eprintln!("Function recursion limit ({}) exceeded", shell_state.max_recursion_depth);
655                    return 1;
656                }
657
658                // Enter function context for local variable scoping
659                shell_state.enter_function();
660
661                // Set up arguments as regular variables (will be enhanced in Phase 2)
662                let old_positional = shell_state.positional_params.clone();
663
664                // Set positional parameters for function arguments
665                shell_state.set_positional_params(args.clone());
666
667                // Execute function body
668                let exit_code = execute(function_body, shell_state);
669
670                // Check if we got an early return from the function
671                if shell_state.is_returning() {
672                    let return_value = shell_state.get_return_value().unwrap_or(0);
673
674                    // Restore old positional parameters
675                    shell_state.set_positional_params(old_positional);
676
677                    // Exit function context
678                    shell_state.exit_function();
679
680                    // Clear return state
681                    shell_state.clear_return();
682
683                    // Return the early return value
684                    return return_value;
685                }
686
687                // Restore old positional parameters
688                shell_state.set_positional_params(old_positional);
689
690                // Exit function context
691                shell_state.exit_function();
692
693                exit_code
694            } else {
695                eprintln!("Function '{}' not found", name);
696                1
697            }
698        }
699        Ast::Return { value } => {
700            // Return statements can only be used inside functions
701            if shell_state.function_depth == 0 {
702                eprintln!("Return statement outside of function");
703                return 1;
704            }
705
706            // Parse return value if provided
707            let exit_code = if let Some(ref val) = value {
708                val.parse::<i32>().unwrap_or(0)
709            } else {
710                0
711            };
712
713            // Set return state to indicate early return from function
714            shell_state.set_return(exit_code);
715
716            // Return the exit code - the function call handler will check for this
717            exit_code
718        }
719        Ast::And { left, right } => {
720            // Execute left side first
721            let left_exit = execute(*left, shell_state);
722            
723            // Check if we got an early return from a function
724            if shell_state.is_returning() {
725                return left_exit;
726            }
727            
728            // Only execute right side if left succeeded (exit code 0)
729            if left_exit == 0 {
730                execute(*right, shell_state)
731            } else {
732                left_exit
733            }
734        }
735        Ast::Or { left, right } => {
736            // Execute left side first
737            let left_exit = execute(*left, shell_state);
738            
739            // Check if we got an early return from a function
740            if shell_state.is_returning() {
741                return left_exit;
742            }
743            
744            // Only execute right side if left failed (exit code != 0)
745            if left_exit != 0 {
746                execute(*right, shell_state)
747            } else {
748                left_exit
749            }
750        }
751    }
752}
753
754fn execute_single_command(cmd: &ShellCommand, shell_state: &mut ShellState) -> i32 {
755    if cmd.args.is_empty() {
756        return 0;
757    }
758
759    // First expand variables, then wildcards
760    let var_expanded_args = expand_variables_in_args(&cmd.args, shell_state);
761    let expanded_args = match expand_wildcards(&var_expanded_args) {
762        Ok(args) => args,
763        Err(_) => return 1,
764    };
765
766    if expanded_args.is_empty() {
767        return 0;
768    }
769
770    // Check if this is a function call
771    if shell_state.get_function(&expanded_args[0]).is_some() {
772        // This is a function call - create a FunctionCall AST node and execute it
773        let function_call = Ast::FunctionCall {
774            name: expanded_args[0].clone(),
775            args: expanded_args[1..].to_vec(),
776        };
777        return execute(function_call, shell_state);
778    }
779
780    if crate::builtins::is_builtin(&expanded_args[0]) {
781        // Create a temporary ShellCommand with expanded args
782        let temp_cmd = ShellCommand {
783            args: expanded_args,
784            input: cmd.input.clone(),
785            output: cmd.output.clone(),
786            append: cmd.append.clone(),
787        };
788        
789        // If we're capturing output, create a writer for it
790        if let Some(ref capture_buffer) = shell_state.capture_output.clone() {
791            // Create a writer that writes to our capture buffer
792            struct CaptureWriter {
793                buffer: Rc<RefCell<Vec<u8>>>,
794            }
795            impl std::io::Write for CaptureWriter {
796                fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
797                    self.buffer.borrow_mut().extend_from_slice(buf);
798                    Ok(buf.len())
799                }
800                fn flush(&mut self) -> std::io::Result<()> {
801                    Ok(())
802                }
803            }
804            let writer = CaptureWriter { buffer: capture_buffer.clone() };
805            crate::builtins::execute_builtin(&temp_cmd, shell_state, Some(Box::new(writer)))
806        } else {
807            crate::builtins::execute_builtin(&temp_cmd, shell_state, None)
808        }
809    } else {
810        let mut command = Command::new(&expanded_args[0]);
811        command.args(&expanded_args[1..]);
812
813        // Set environment for child process
814        let child_env = shell_state.get_env_for_child();
815        command.env_clear();
816        for (key, value) in child_env {
817            command.env(key, value);
818        }
819        
820        // If we're capturing output, redirect stdout to capture buffer
821        let capturing = shell_state.capture_output.is_some();
822        if capturing {
823            command.stdout(Stdio::piped());
824        }
825
826        // Handle input redirection
827        if let Some(ref input_file) = cmd.input {
828            let expanded_input = expand_variables_in_string(input_file, shell_state);
829            match File::open(&expanded_input) {
830                Ok(file) => {
831                    command.stdin(Stdio::from(file));
832                }
833                Err(e) => {
834                    if shell_state.colors_enabled {
835                        eprintln!(
836                            "{}Error opening input file '{}{}",
837                            shell_state.color_scheme.error,
838                            input_file,
839                            &format!("': {}\x1b[0m", e)
840                        );
841                    } else {
842                        eprintln!("Error opening input file '{}': {}", input_file, e);
843                    }
844                    return 1;
845                }
846            }
847        }
848
849        // Handle output redirection
850        if let Some(ref output_file) = cmd.output {
851            let expanded_output = expand_variables_in_string(output_file, shell_state);
852            match File::create(&expanded_output) {
853                Ok(file) => {
854                    command.stdout(Stdio::from(file));
855                }
856                Err(e) => {
857                    if shell_state.colors_enabled {
858                        eprintln!(
859                            "{}Error creating output file '{}{}",
860                            shell_state.color_scheme.error,
861                            output_file,
862                            &format!("': {}\x1b[0m", e)
863                        );
864                    } else {
865                        eprintln!("Error creating output file '{}': {}", output_file, e);
866                    }
867                    return 1;
868                }
869            }
870        } else if let Some(ref append_file) = cmd.append {
871            let expanded_append = expand_variables_in_string(append_file, shell_state);
872            match File::options().append(true).create(true).open(&expanded_append) {
873                Ok(file) => {
874                    command.stdout(Stdio::from(file));
875                }
876                Err(e) => {
877                    if shell_state.colors_enabled {
878                        eprintln!(
879                            "{}Error opening append file '{}{}",
880                            shell_state.color_scheme.error,
881                            append_file,
882                            &format!("': {}\x1b[0m", e)
883                        );
884                    } else {
885                        eprintln!("Error opening append file '{}': {}", append_file, e);
886                    }
887                    return 1;
888                }
889            }
890        }
891
892        match command.spawn() {
893            Ok(mut child) => {
894                // If capturing, read stdout
895                if capturing
896                    && let Some(mut stdout) = child.stdout.take() {
897                        use std::io::Read;
898                        let mut output = Vec::new();
899                        if stdout.read_to_end(&mut output).is_ok()
900                            && let Some(ref capture_buffer) = shell_state.capture_output {
901                                capture_buffer.borrow_mut().extend_from_slice(&output);
902                            }
903                    }
904                
905                match child.wait() {
906                    Ok(status) => status.code().unwrap_or(0),
907                    Err(e) => {
908                    if shell_state.colors_enabled {
909                        eprintln!(
910                            "{}Error waiting for command: {}\x1b[0m",
911                            shell_state.color_scheme.error,
912                            e
913                        );
914                    } else {
915                        eprintln!("Error waiting for command: {}", e);
916                    }
917                        1
918                    }
919                }
920            }
921            Err(e) => {
922                if shell_state.colors_enabled {
923                    eprintln!(
924                        "{}Command spawn error: {}\x1b[0m",
925                        shell_state.color_scheme.error, e
926                    );
927                } else {
928                    eprintln!("Command spawn error: {}", e);
929                }
930                1
931            }
932        }
933    }
934}
935
936fn execute_pipeline(commands: &[ShellCommand], shell_state: &mut ShellState) -> i32 {
937    let mut exit_code = 0;
938    let mut previous_stdout = None;
939
940    for (i, cmd) in commands.iter().enumerate() {
941        if cmd.args.is_empty() {
942            continue;
943        }
944
945        let is_last = i == commands.len() - 1;
946
947        // First expand variables, then wildcards
948        let var_expanded_args = expand_variables_in_args(&cmd.args, shell_state);
949        let expanded_args = match expand_wildcards(&var_expanded_args) {
950            Ok(args) => args,
951            Err(_) => return 1,
952        };
953
954        if expanded_args.is_empty() {
955            continue;
956        }
957
958        if crate::builtins::is_builtin(&expanded_args[0]) {
959            // Built-ins in pipelines are tricky - for now, execute them separately
960            // This is not perfect but better than nothing
961            let temp_cmd = ShellCommand {
962                args: expanded_args,
963                input: cmd.input.clone(),
964                output: cmd.output.clone(),
965                append: cmd.append.clone(),
966            };
967            if !is_last {
968                // Create a safe pipe
969                let (reader, writer) = match pipe() {
970                    Ok(p) => p,
971                    Err(e) => {
972                        if shell_state.colors_enabled {
973                            eprintln!(
974                                "{}Error creating pipe for builtin: {}\x1b[0m",
975                                shell_state.color_scheme.error,
976                                e
977                            );
978                        } else {
979                            eprintln!("Error creating pipe for builtin: {}", e);
980                        }
981                        return 1;
982                    }
983                };
984                // Execute builtin with writer for output capture
985                exit_code = crate::builtins::execute_builtin(
986                    &temp_cmd,
987                    shell_state,
988                    Some(Box::new(writer)),
989                );
990                // Use reader for next command's stdin
991                previous_stdout = Some(Stdio::from(reader));
992            } else {
993                // Last command: no need to pipe output
994                exit_code = crate::builtins::execute_builtin(&temp_cmd, shell_state, None);
995                previous_stdout = None;
996            }
997        } else {
998            let mut command = Command::new(&expanded_args[0]);
999            command.args(&expanded_args[1..]);
1000
1001            // Set environment for child process
1002            let child_env = shell_state.get_env_for_child();
1003            command.env_clear();
1004            for (key, value) in child_env {
1005                command.env(key, value);
1006            }
1007
1008            // Set stdin from previous command's stdout
1009            if let Some(prev) = previous_stdout.take() {
1010                command.stdin(prev);
1011            }
1012
1013            // Set stdout for next command, unless this is the last
1014            if !is_last {
1015                command.stdout(Stdio::piped());
1016            }
1017
1018            // Handle input redirection (only for first command)
1019            if i == 0
1020                && let Some(ref input_file) = cmd.input {
1021                    let expanded_input = expand_variables_in_string(input_file, shell_state);
1022                    match File::open(&expanded_input) {
1023                        Ok(file) => {
1024                            command.stdin(Stdio::from(file));
1025                        }
1026                        Err(e) => {
1027                            if shell_state.colors_enabled {
1028                                eprintln!(
1029                                    "{}Error opening input file '{}{}",
1030                                    shell_state.color_scheme.error,
1031                                    input_file,
1032                                    &format!("': {}\x1b[0m", e)
1033                                );
1034                            } else {
1035                                eprintln!("Error opening input file '{}': {}", input_file, e);
1036                            }
1037                            return 1;
1038                        }
1039                    }
1040                }
1041
1042            // Handle output redirection (only for last command)
1043            if is_last {
1044                if let Some(ref output_file) = cmd.output {
1045                    let expanded_output = expand_variables_in_string(output_file, shell_state);
1046                    match File::create(&expanded_output) {
1047                        Ok(file) => {
1048                            command.stdout(Stdio::from(file));
1049                        }
1050                        Err(e) => {
1051                            if shell_state.colors_enabled {
1052                                eprintln!(
1053                                    "{}Error creating output file '{}{}",
1054                                    shell_state.color_scheme.error,
1055                                    output_file,
1056                                    &format!("': {}\x1b[0m", e)
1057                                );
1058                            } else {
1059                                eprintln!("Error creating output file '{}': {}", output_file, e);
1060                            }
1061                            return 1;
1062                        }
1063                    }
1064                } else if let Some(ref append_file) = cmd.append {
1065                    let expanded_append = expand_variables_in_string(append_file, shell_state);
1066                    match File::options().append(true).create(true).open(&expanded_append) {
1067                        Ok(file) => {
1068                            command.stdout(Stdio::from(file));
1069                        }
1070                        Err(e) => {
1071                            if shell_state.colors_enabled {
1072                                eprintln!(
1073                                    "{}Error opening append file '{}{}",
1074                                    shell_state.color_scheme.error,
1075                                    append_file,
1076                                    &format!("': {}\x1b[0m", e)
1077                                );
1078                            } else {
1079                                eprintln!("Error opening append file '{}': {}", append_file, e);
1080                            }
1081                            return 1;
1082                        }
1083                    }
1084                }
1085            }
1086
1087            match command.spawn() {
1088                Ok(mut child) => {
1089                    if !is_last {
1090                        previous_stdout = child.stdout.take().map(Stdio::from);
1091                    }
1092                    match child.wait() {
1093                        Ok(status) => {
1094                            exit_code = status.code().unwrap_or(0);
1095                        }
1096                        Err(e) => {
1097                            if shell_state.colors_enabled {
1098                                eprintln!(
1099                                    "{}Error waiting for command: {}\x1b[0m",
1100                                    shell_state.color_scheme.error,
1101                                    e
1102                                );
1103                            } else {
1104                                eprintln!("Error waiting for command: {}", e);
1105                            }
1106                            exit_code = 1;
1107                        }
1108                    }
1109                }
1110                Err(e) => {
1111                    if shell_state.colors_enabled {
1112                        eprintln!(
1113                            "{}Error spawning command '{}{}",
1114                            shell_state.color_scheme.error,
1115                            expanded_args[0],
1116                            &format!("': {}\x1b[0m", e)
1117                        );
1118                    } else {
1119                        eprintln!("Error spawning command '{}': {}", expanded_args[0], e);
1120                    }
1121                    exit_code = 1;
1122                }
1123            }
1124        }
1125    }
1126
1127    exit_code
1128}
1129
1130#[cfg(test)]
1131mod tests {
1132    use super::*;
1133
1134    #[test]
1135    fn test_execute_single_command_builtin() {
1136        let cmd = ShellCommand {
1137            args: vec!["true".to_string()],
1138            input: None,
1139            output: None,
1140            append: None,
1141        };
1142        let mut shell_state = crate::state::ShellState::new();
1143        let exit_code = execute_single_command(&cmd, &mut shell_state);
1144        assert_eq!(exit_code, 0);
1145    }
1146
1147    // For external commands, test with a command that exists
1148    #[test]
1149    fn test_execute_single_command_external() {
1150        let cmd = ShellCommand {
1151            args: vec!["true".to_string()], // Assume true exists
1152            input: None,
1153            output: None,
1154            append: None,
1155        };
1156        let mut shell_state = crate::state::ShellState::new();
1157        let exit_code = execute_single_command(&cmd, &mut shell_state);
1158        assert_eq!(exit_code, 0);
1159    }
1160
1161    #[test]
1162    fn test_execute_single_command_external_nonexistent() {
1163        let cmd = ShellCommand {
1164            args: vec!["nonexistent_command".to_string()],
1165            input: None,
1166            output: None,
1167            append: None,
1168        };
1169        let mut shell_state = crate::state::ShellState::new();
1170        let exit_code = execute_single_command(&cmd, &mut shell_state);
1171        assert_eq!(exit_code, 1); // Command not found
1172    }
1173
1174    #[test]
1175    fn test_execute_pipeline() {
1176        let commands = vec![
1177            ShellCommand {
1178                args: vec!["printf".to_string(), "hello".to_string()],
1179                input: None,
1180                output: None,
1181                append: None,
1182            },
1183            ShellCommand {
1184                args: vec!["cat".to_string()], // cat reads from stdin
1185                input: None,
1186                output: None,
1187                append: None,
1188            },
1189        ];
1190        let mut shell_state = crate::state::ShellState::new();
1191        let exit_code = execute_pipeline(&commands, &mut shell_state);
1192        assert_eq!(exit_code, 0);
1193    }
1194
1195    #[test]
1196    fn test_execute_empty_pipeline() {
1197        let commands = vec![];
1198        let mut shell_state = crate::state::ShellState::new();
1199        let exit_code = execute(Ast::Pipeline(commands), &mut shell_state);
1200        assert_eq!(exit_code, 0);
1201    }
1202
1203    #[test]
1204    fn test_execute_single_command() {
1205        let ast = Ast::Pipeline(vec![ShellCommand {
1206            args: vec!["true".to_string()],
1207            input: None,
1208            output: None,
1209            append: None,
1210        }]);
1211        let mut shell_state = crate::state::ShellState::new();
1212        let exit_code = execute(ast, &mut shell_state);
1213        assert_eq!(exit_code, 0);
1214    }
1215
1216    #[test]
1217    fn test_execute_function_definition() {
1218        let ast = Ast::FunctionDefinition {
1219            name: "test_func".to_string(),
1220            body: Box::new(Ast::Pipeline(vec![ShellCommand {
1221                args: vec!["echo".to_string(), "hello".to_string()],
1222                input: None,
1223                output: None,
1224                append: None,
1225            }])),
1226        };
1227        let mut shell_state = crate::state::ShellState::new();
1228        let exit_code = execute(ast, &mut shell_state);
1229        assert_eq!(exit_code, 0);
1230
1231        // Check that function was stored
1232        assert!(shell_state.get_function("test_func").is_some());
1233    }
1234
1235    #[test]
1236    fn test_execute_function_call() {
1237        // First define a function
1238        let mut shell_state = crate::state::ShellState::new();
1239        shell_state.define_function(
1240            "test_func".to_string(),
1241            Ast::Pipeline(vec![ShellCommand {
1242                args: vec!["echo".to_string(), "hello".to_string()],
1243                input: None,
1244                output: None,
1245                append: None,
1246            }]),
1247        );
1248
1249        // Now call the function
1250        let ast = Ast::FunctionCall {
1251            name: "test_func".to_string(),
1252            args: vec![],
1253        };
1254        let exit_code = execute(ast, &mut shell_state);
1255        assert_eq!(exit_code, 0);
1256    }
1257
1258    #[test]
1259    fn test_execute_function_call_with_args() {
1260        // First define a function that uses arguments
1261        let mut shell_state = crate::state::ShellState::new();
1262        shell_state.define_function(
1263            "test_func".to_string(),
1264            Ast::Pipeline(vec![ShellCommand {
1265                args: vec!["echo".to_string(), "arg1".to_string()],
1266                input: None,
1267                output: None,
1268                append: None,
1269            }]),
1270        );
1271
1272        // Now call the function with arguments
1273        let ast = Ast::FunctionCall {
1274            name: "test_func".to_string(),
1275            args: vec!["hello".to_string()],
1276        };
1277        let exit_code = execute(ast, &mut shell_state);
1278        assert_eq!(exit_code, 0);
1279    }
1280
1281    #[test]
1282    fn test_execute_nonexistent_function() {
1283        let mut shell_state = crate::state::ShellState::new();
1284        let ast = Ast::FunctionCall {
1285            name: "nonexistent".to_string(),
1286            args: vec![],
1287        };
1288        let exit_code = execute(ast, &mut shell_state);
1289        assert_eq!(exit_code, 1); // Should return error code
1290    }
1291
1292    #[test]
1293    fn test_execute_function_integration() {
1294        // Test full integration: define function, then call it
1295        let mut shell_state = crate::state::ShellState::new();
1296
1297        // First define a function
1298        let define_ast = Ast::FunctionDefinition {
1299            name: "hello".to_string(),
1300            body: Box::new(Ast::Pipeline(vec![ShellCommand {
1301                args: vec!["printf".to_string(), "Hello from function".to_string()],
1302                input: None,
1303                output: None,
1304                append: None,
1305            }])),
1306        };
1307        let exit_code = execute(define_ast, &mut shell_state);
1308        assert_eq!(exit_code, 0);
1309
1310        // Now call the function
1311        let call_ast = Ast::FunctionCall {
1312            name: "hello".to_string(),
1313            args: vec![],
1314        };
1315        let exit_code = execute(call_ast, &mut shell_state);
1316        assert_eq!(exit_code, 0);
1317    }
1318
1319    #[test]
1320    fn test_execute_function_with_local_variables() {
1321        let mut shell_state = crate::state::ShellState::new();
1322
1323        // Set a global variable
1324        shell_state.set_var("global_var", "global_value".to_string());
1325
1326        // Define a function that uses local variables
1327        let define_ast = Ast::FunctionDefinition {
1328            name: "test_func".to_string(),
1329            body: Box::new(Ast::Sequence(vec![
1330                Ast::LocalAssignment {
1331                    var: "local_var".to_string(),
1332                    value: "local_value".to_string(),
1333                },
1334                Ast::Assignment {
1335                    var: "global_var".to_string(),
1336                    value: "modified_in_function".to_string(),
1337                },
1338                Ast::Pipeline(vec![ShellCommand {
1339                    args: vec!["printf".to_string(), "success".to_string()],
1340                    input: None,
1341                    output: None,
1342                    append: None,
1343                }]),
1344            ])),
1345        };
1346        let exit_code = execute(define_ast, &mut shell_state);
1347        assert_eq!(exit_code, 0);
1348
1349        // Global variable should not be modified during function definition
1350        assert_eq!(shell_state.get_var("global_var"), Some("global_value".to_string()));
1351
1352        // Call the function
1353        let call_ast = Ast::FunctionCall {
1354            name: "test_func".to_string(),
1355            args: vec![],
1356        };
1357        let exit_code = execute(call_ast, &mut shell_state);
1358        assert_eq!(exit_code, 0);
1359
1360        // After function call, global variable should be modified since function assignments affect global scope
1361        assert_eq!(shell_state.get_var("global_var"), Some("modified_in_function".to_string()));
1362    }
1363
1364    #[test]
1365    fn test_execute_nested_function_calls() {
1366        let mut shell_state = crate::state::ShellState::new();
1367
1368        // Set global variable
1369        shell_state.set_var("global_var", "global".to_string());
1370
1371        // Define outer function
1372        let outer_func = Ast::FunctionDefinition {
1373            name: "outer".to_string(),
1374            body: Box::new(Ast::Sequence(vec![
1375                Ast::Assignment {
1376                    var: "global_var".to_string(),
1377                    value: "outer_modified".to_string(),
1378                },
1379                Ast::FunctionCall {
1380                    name: "inner".to_string(),
1381                    args: vec![],
1382                },
1383                Ast::Pipeline(vec![ShellCommand {
1384                    args: vec!["printf".to_string(), "outer_done".to_string()],
1385                    input: None,
1386                    output: None,
1387                    append: None,
1388                }]),
1389            ])),
1390        };
1391
1392        // Define inner function
1393        let inner_func = Ast::FunctionDefinition {
1394            name: "inner".to_string(),
1395            body: Box::new(Ast::Sequence(vec![
1396                Ast::Assignment {
1397                    var: "global_var".to_string(),
1398                    value: "inner_modified".to_string(),
1399                },
1400                Ast::Pipeline(vec![ShellCommand {
1401                    args: vec!["printf".to_string(), "inner_done".to_string()],
1402                    input: None,
1403                    output: None,
1404                    append: None,
1405                }]),
1406            ])),
1407        };
1408
1409        // Define both functions
1410        execute(outer_func, &mut shell_state);
1411        execute(inner_func, &mut shell_state);
1412
1413        // Set initial global value
1414        shell_state.set_var("global_var", "initial".to_string());
1415
1416        // Call outer function (which calls inner function)
1417        let call_ast = Ast::FunctionCall {
1418            name: "outer".to_string(),
1419            args: vec![],
1420        };
1421        let exit_code = execute(call_ast, &mut shell_state);
1422        assert_eq!(exit_code, 0);
1423
1424        // After nested function calls, global variable should be modified by inner function
1425        // (bash behavior: function variable assignments affect global scope)
1426        assert_eq!(shell_state.get_var("global_var"), Some("inner_modified".to_string()));
1427    }
1428}