rush_sh/
executor.rs

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