mxsh 0.2.0

Embeddable POSIX-style shell parser and runtime
Documentation
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);
}