use super::parser::Ast;
use super::state::ShellState;
mod expansion;
mod redirection;
mod command;
mod subshell;
mod async_exec;
pub use expansion::expand_variables_in_string;
pub(crate) use command::{execute_and_capture_output, execute_single_command, execute_pipeline};
pub(crate) use subshell::{execute_compound_with_redirections, execute_compound_in_pipeline};
pub(crate) use async_exec::execute_async;
pub fn execute_trap_handler(trap_cmd: &str, shell_state: &mut ShellState) -> i32 {
let saved_exit_code = shell_state.last_exit_code;
let result = match crate::lexer::lex(trap_cmd, shell_state) {
Ok(tokens) => {
match crate::lexer::expand_aliases(
tokens,
shell_state,
&mut std::collections::HashSet::new(),
) {
Ok(expanded_tokens) => {
match crate::parser::parse(expanded_tokens) {
Ok(ast) => execute(ast, shell_state),
Err(_) => {
saved_exit_code
}
}
}
Err(_) => {
saved_exit_code
}
}
}
Err(_) => {
saved_exit_code
}
};
shell_state.last_exit_code = saved_exit_code;
result
}
pub fn execute(ast: Ast, shell_state: &mut ShellState) -> i32 {
match ast {
Ast::Assignment { var, value } => {
if shell_state.options.noexec {
return 0; }
let expanded_value = expand_variables_in_string(&value, shell_state);
shell_state.set_var(&var, expanded_value.clone());
if shell_state.options.allexport {
shell_state.export_var(&var);
}
0
}
Ast::LocalAssignment { var, value } => {
if shell_state.options.noexec {
return 0; }
let expanded_value = expand_variables_in_string(&value, shell_state);
shell_state.set_local_var(&var, expanded_value);
0
}
Ast::Pipeline(commands) => {
if commands.is_empty() {
return 0;
}
if commands.len() == 1 {
execute_single_command(&commands[0], shell_state)
} else {
execute_pipeline(&commands, shell_state)
}
}
Ast::Sequence(asts) => {
let mut exit_code = 0;
for ast in asts {
shell_state.last_was_negation = false;
exit_code = execute(ast, shell_state);
if shell_state.is_returning() {
return exit_code;
}
if shell_state.exit_requested {
return shell_state.exit_code;
}
if shell_state.is_breaking() || shell_state.is_continuing() {
return exit_code;
}
if shell_state.options.errexit
&& exit_code != 0
&& !shell_state.in_condition
&& !shell_state.in_logical_chain
&& !shell_state.in_negation
&& !shell_state.last_was_negation {
shell_state.exit_requested = true;
shell_state.exit_code = exit_code;
return exit_code;
}
}
exit_code
}
Ast::If {
branches,
else_branch,
} => {
for (condition, then_branch) in branches {
shell_state.in_condition = true;
let cond_exit = execute(*condition, shell_state);
shell_state.in_condition = false;
if cond_exit == 0 {
let exit_code = execute(*then_branch, shell_state);
if shell_state.is_returning() {
return exit_code;
}
return exit_code;
}
}
if let Some(else_b) = else_branch {
let exit_code = execute(*else_b, shell_state);
if shell_state.is_returning() {
return exit_code;
}
exit_code
} else {
0
}
}
Ast::Case {
word,
cases,
default,
} => {
for (patterns, branch) in cases {
for pattern in &patterns {
if let Ok(glob_pattern) = glob::Pattern::new(pattern) {
if glob_pattern.matches(&word) {
let exit_code = execute(branch, shell_state);
if shell_state.is_returning() {
return exit_code;
}
return exit_code;
}
} else {
if &word == pattern {
let exit_code = execute(branch, shell_state);
if shell_state.is_returning() {
return exit_code;
}
return exit_code;
}
}
}
}
if let Some(def) = default {
let exit_code = execute(*def, shell_state);
if shell_state.is_returning() {
return exit_code;
}
exit_code
} else {
0
}
}
Ast::For {
variable,
items,
body,
} => {
let mut exit_code = 0;
shell_state.enter_loop();
let mut expanded_items = Vec::new();
for item in items {
let expanded = expand_variables_in_string(&item, shell_state);
for word in expanded.split_whitespace() {
expanded_items.push(word.to_string());
}
}
for item in expanded_items {
crate::state::process_pending_signals(shell_state);
if shell_state.exit_requested {
shell_state.exit_loop();
return shell_state.exit_code;
}
shell_state.set_var(&variable, item.clone());
exit_code = execute(*body.clone(), shell_state);
if shell_state.is_returning() {
shell_state.exit_loop();
return exit_code;
}
if shell_state.exit_requested {
shell_state.exit_loop();
return shell_state.exit_code;
}
if shell_state.is_breaking() {
if shell_state.get_break_level() == 1 {
shell_state.clear_break();
break;
} else {
shell_state.decrement_break_level();
break;
}
}
if shell_state.is_continuing() {
if shell_state.get_continue_level() == 1 {
shell_state.clear_continue();
continue;
} else {
shell_state.decrement_continue_level();
break; }
}
}
shell_state.exit_loop();
exit_code
}
Ast::While { condition, body } => {
let mut exit_code = 0;
shell_state.enter_loop();
loop {
shell_state.in_condition = true;
let cond_exit = execute(*condition.clone(), shell_state);
shell_state.in_condition = false;
if shell_state.is_returning() {
shell_state.exit_loop();
return cond_exit;
}
if shell_state.exit_requested {
shell_state.exit_loop();
return shell_state.exit_code;
}
if cond_exit != 0 {
break;
}
exit_code = execute(*body.clone(), shell_state);
if shell_state.is_returning() {
shell_state.exit_loop();
return exit_code;
}
if shell_state.exit_requested {
shell_state.exit_loop();
return shell_state.exit_code;
}
if shell_state.is_breaking() {
if shell_state.get_break_level() == 1 {
shell_state.clear_break();
break;
} else {
shell_state.decrement_break_level();
break;
}
}
if shell_state.is_continuing() {
if shell_state.get_continue_level() == 1 {
shell_state.clear_continue();
continue;
} else {
shell_state.decrement_continue_level();
break; }
}
}
shell_state.exit_loop();
exit_code
}
Ast::Until { condition, body } => {
let mut exit_code = 0;
shell_state.enter_loop();
loop {
shell_state.in_condition = true;
let cond_exit = execute(*condition.clone(), shell_state);
shell_state.in_condition = false;
if shell_state.is_returning() {
shell_state.exit_loop();
return cond_exit;
}
if shell_state.exit_requested {
shell_state.exit_loop();
return shell_state.exit_code;
}
if cond_exit == 0 {
break;
}
exit_code = execute(*body.clone(), shell_state);
if shell_state.is_returning() {
shell_state.exit_loop();
return exit_code;
}
if shell_state.exit_requested {
shell_state.exit_loop();
return shell_state.exit_code;
}
if shell_state.is_breaking() {
if shell_state.get_break_level() == 1 {
shell_state.clear_break();
break;
} else {
shell_state.decrement_break_level();
break;
}
}
if shell_state.is_continuing() {
if shell_state.get_continue_level() == 1 {
shell_state.clear_continue();
continue;
} else {
shell_state.decrement_continue_level();
break; }
}
}
shell_state.exit_loop();
exit_code
}
Ast::FunctionDefinition { name, body } => {
shell_state.define_function(name.clone(), *body);
0
}
Ast::FunctionCall { name, args } => {
if let Some(function_body) = shell_state.get_function(&name).cloned() {
if shell_state.function_depth >= shell_state.max_recursion_depth {
eprintln!(
"Function recursion limit ({}) exceeded",
shell_state.max_recursion_depth
);
return 1;
}
shell_state.enter_function();
let old_positional = shell_state.positional_params.clone();
shell_state.set_positional_params(args.clone());
shell_state.line_number_stack.push(shell_state.current_line_number);
shell_state.current_line_number = 1;
let exit_code = execute(function_body, shell_state);
let restore_line_number = |state: &mut ShellState| {
if let Some(saved_line) = state.line_number_stack.pop() {
state.current_line_number = saved_line;
}
};
if shell_state.is_returning() {
let return_value = shell_state.get_return_value().unwrap_or(0);
shell_state.set_positional_params(old_positional);
restore_line_number(shell_state);
shell_state.exit_function();
shell_state.clear_return();
shell_state.last_exit_code = return_value;
return return_value;
}
shell_state.set_positional_params(old_positional);
if let Some(saved_line) = shell_state.line_number_stack.pop() {
shell_state.current_line_number = saved_line;
}
shell_state.exit_function();
shell_state.last_exit_code = exit_code;
exit_code
} else {
eprintln!("Function '{}' not found", name);
1
}
}
Ast::Return { value } => {
if shell_state.function_depth == 0 {
eprintln!("Return statement outside of function");
return 1;
}
let exit_code = if let Some(ref val) = value {
val.parse::<i32>().unwrap_or(0)
} else {
0
};
shell_state.set_return(exit_code);
exit_code
}
Ast::And { left, right } => {
shell_state.in_logical_chain = true;
let left_exit = execute(*left, shell_state);
if shell_state.is_returning()
|| shell_state.exit_requested
|| shell_state.is_breaking()
|| shell_state.is_continuing()
{
shell_state.in_logical_chain = false;
return left_exit;
}
let result = if left_exit == 0 {
execute(*right, shell_state)
} else {
left_exit
};
shell_state.in_logical_chain = false;
result
}
Ast::Or { left, right } => {
shell_state.in_logical_chain = true;
let left_exit = execute(*left, shell_state);
if shell_state.is_returning()
|| shell_state.exit_requested
|| shell_state.is_breaking()
|| shell_state.is_continuing()
{
shell_state.in_logical_chain = false;
return left_exit;
}
let result = if left_exit != 0 {
execute(*right, shell_state)
} else {
left_exit
};
shell_state.in_logical_chain = false;
result
}
Ast::Negation { command } => {
shell_state.in_negation = true;
let exit_code = execute(*command, shell_state);
shell_state.in_negation = false;
shell_state.last_was_negation = true;
let inverted_code = if exit_code == 0 { 1 } else { 0 };
shell_state.last_exit_code = inverted_code;
inverted_code
}
Ast::Subshell { body } => {
let exit_code = subshell::execute_subshell(*body, shell_state);
if shell_state.options.errexit
&& exit_code != 0
&& !shell_state.in_condition
&& !shell_state.in_logical_chain
&& !shell_state.in_negation {
shell_state.exit_requested = true;
shell_state.exit_code = exit_code;
}
exit_code
}
Ast::CommandGroup { body } => execute(*body, shell_state),
Ast::AsyncCommand { command } => {
execute_async(*command, shell_state)
}
}
}
#[cfg(test)]
mod tests;