use super::*;
enum LoopBranchAction {
None,
Continue,
Break,
}
fn handle_loop_branch(state: &mut ShellState) -> LoopBranchAction {
match state.control_flow {
ControlFlow::Break(n) => {
if n <= 1 || n > state.loop_depth {
state.control_flow = ControlFlow::None;
} else {
state.control_flow = ControlFlow::Break(n - 1);
}
LoopBranchAction::Break
}
ControlFlow::Continue(n) => {
if n <= 1 || n > state.loop_depth {
state.control_flow = ControlFlow::None;
LoopBranchAction::Continue
} else {
state.control_flow = ControlFlow::Continue(n - 1);
LoopBranchAction::Break
}
}
ControlFlow::Return(_) | ControlFlow::Exit(_) => LoopBranchAction::Break,
ControlFlow::None => LoopBranchAction::None,
}
}
pub(super) fn run_loop_core<R: Runtime, F>(
state: &mut ShellState,
runtime: &mut R,
mut body_step: F,
) -> i32
where
F: FnMut(&mut ShellState, &mut R) -> Option<i32>,
{
state.loop_depth += 1;
let mut status = 0;
while let Some(body_status) = body_step(state, runtime) {
status = body_status;
state.set_last_status(status);
match handle_loop_branch(state) {
LoopBranchAction::Continue => continue,
LoopBranchAction::Break => break,
LoopBranchAction::None => {}
}
if state.exit_code >= 0 {
break;
}
}
state.loop_depth -= 1;
status
}
pub(super) fn run_if_clause_with<R: Runtime>(
state: &mut ShellState,
runtime: &mut R,
if_clause: &IfClause,
run_lists: fn(&mut ShellState, &mut R, &[CommandList]) -> i32,
) -> i32 {
state.push_errexit_context(ErrexitContext::IfCondition);
let cond_status = run_lists(state, runtime, if_clause.condition());
state.pop_errexit_context();
state.set_last_status(cond_status);
if state.control_flow != ControlFlow::None || state.exit_code >= 0 {
return cond_status;
}
if cond_status == 0 {
run_lists(state, runtime, if_clause.body())
} else {
match if_clause.else_part() {
Some(boxed) => match boxed {
ElsePart::Elif(if_clause) => {
run_if_clause_with(state, runtime, if_clause, run_lists)
}
ElsePart::Else(body) => run_lists(state, runtime, body.body()),
},
None => 0,
}
}
}
pub(super) fn run_for_clause_with<R: Runtime>(
state: &mut ShellState,
runtime: &mut R,
clause: &ForClause,
run_lists: fn(&mut ShellState, &mut R, &[CommandList]) -> i32,
) -> i32 {
if !shell_builtins::is_valid_identifier(clause.name()) {
shell_errln(
state,
&format!("for: {}: invalid identifier", clause.name()),
);
if !state.interactive {
state.set_exit_code(2);
}
return 2;
}
let words: Vec<String> = if clause.has_in() {
clause
.word_list()
.iter()
.flat_map(|word| shell_expand::expand_for_word(state, runtime, word))
.collect()
} else {
state.variable_store.frame.iter().skip(1).cloned().collect()
};
let mut iter = words.iter();
run_loop_core(state, runtime, |state, runtime| {
let word_val = iter.next()?;
let attrib = if state.has_option(OPT_ALLEXPORT) {
VAR_EXPORT
} else {
0
};
if !state.env_set(clause.name(), word_val.clone(), attrib) {
shell_errln(state, &format!("{}: readonly variable", clause.name()));
if !state.interactive {
state.set_exit_code(1);
}
return Some(1);
}
Some(run_lists(state, runtime, clause.body()))
})
}
pub(super) fn run_loop_clause_iteration<R: Runtime>(
state: &mut ShellState,
runtime: &mut R,
clause: &LoopClause,
run_lists: fn(&mut ShellState, &mut R, &[CommandList]) -> i32,
) -> Option<i32> {
state.push_errexit_context(ErrexitContext::LoopCondition);
let cond_status = run_lists(state, runtime, clause.condition());
state.pop_errexit_context();
if state.control_flow != ControlFlow::None || state.exit_code >= 0 {
return Some(cond_status);
}
let should_run = match clause.loop_type() {
LoopType::While => cond_status == 0,
LoopType::Until => cond_status != 0,
};
if !should_run {
return None;
}
Some(run_lists(state, runtime, clause.body()))
}
pub(super) fn run_case_clause_with<R: Runtime>(
state: &mut ShellState,
runtime: &mut R,
clause: &CaseClause,
run_lists: fn(&mut ShellState, &mut R, &[CommandList]) -> i32,
) -> i32 {
let word_val = shell_expand::expand_case_word(state, runtime, clause.word());
for item in clause.items() {
for pat in item.patterns() {
let pat_val = shell_expand::expand_case_pattern(state, runtime, pat);
if shell_expand::pattern_match(&pat_val, &word_val) {
return run_lists(state, runtime, item.body());
}
}
}
0
}
pub(super) fn complete_command_list_step<R: Runtime>(
state: &mut ShellState,
runtime: &mut R,
status: i32,
) {
state.set_last_status(status);
let status_exempt = state.take_next_status_errexit_exempt();
if state.has_option(OPT_ERREXIT) && status != 0 && !status_exempt && !state.errexit_is_exempt()
{
state.set_exit_code_if_unset(status);
}
if state.has_option(OPT_NOTIFY) {
shell_jobs::maybe_notify_jobs(state, runtime);
}
shell_traps::run_pending_traps(state, runtime);
}