Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
217 changes: 92 additions & 125 deletions crates/bashkit/src/interpreter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,16 @@ use crate::error::Result;
use crate::fs::FileSystem;
use crate::limits::{ExecutionCounters, ExecutionLimits, SessionLimits};

/// Action to take after processing a loop body iteration's control flow.
enum LoopAction {
/// Continue to the next loop iteration.
Continue,
/// Break out of the current loop.
Break,
/// Return an ExecResult immediately (propagated break N, continue N, or return).
Return(ExecResult),
}

/// A single command history entry.
#[derive(Debug, Clone)]
pub struct HistoryEntry {
Expand Down Expand Up @@ -1517,51 +1527,17 @@ impl Interpreter {
stdout.push_str(&result.stdout);
stderr.push_str(&result.stderr);
exit_code = result.exit_code;
last_errexit_suppressed = result.errexit_suppressed;

// Check for break/continue
match result.control_flow {
ControlFlow::Break(n) => {
if n <= 1 {
break;
} else {
// Propagate break with decremented count
return Ok(ExecResult {
stdout,
stderr,
exit_code,
control_flow: ControlFlow::Break(n - 1),
..Default::default()
});
}
}
ControlFlow::Continue(n) => {
if n <= 1 {
continue;
} else {
// Propagate continue with decremented count
return Ok(ExecResult {
stdout,
stderr,
exit_code,
control_flow: ControlFlow::Continue(n - 1),
..Default::default()
});
}
}
ControlFlow::Return(code) => {
// Propagate return
return Ok(ExecResult {
stdout,
stderr,
exit_code: code,
control_flow: ControlFlow::Return(code),
..Default::default()
});
}
ControlFlow::None => {
// errexit is already handled by execute_command_sequence_impl
}
match Self::handle_loop_control_flow(
&stdout,
&stderr,
exit_code,
&mut last_errexit_suppressed,
&result,
) {
LoopAction::Continue => {}
LoopAction::Break => break,
LoopAction::Return(r) => return Ok(r),
}
}

Expand Down Expand Up @@ -1784,47 +1760,17 @@ impl Interpreter {
stdout.push_str(&result.stdout);
stderr.push_str(&result.stderr);
exit_code = result.exit_code;
last_errexit_suppressed = result.errexit_suppressed;

// Check for break/continue
match result.control_flow {
ControlFlow::Break(n) => {
if n <= 1 {
break;
} else {
return Ok(ExecResult {
stdout,
stderr,
exit_code,
control_flow: ControlFlow::Break(n - 1),
..Default::default()
});
}
}
ControlFlow::Continue(n) => {
if n > 1 {
return Ok(ExecResult {
stdout,
stderr,
exit_code,
control_flow: ControlFlow::Continue(n - 1),
..Default::default()
});
}
// n <= 1: continue to next iteration (after step)
}
ControlFlow::Return(code) => {
return Ok(ExecResult {
stdout,
stderr,
exit_code: code,
control_flow: ControlFlow::Return(code),
..Default::default()
});
}
ControlFlow::None => {
// errexit is already handled by execute_command_sequence_impl
}
match Self::handle_loop_control_flow(
&stdout,
&stderr,
exit_code,
&mut last_errexit_suppressed,
&result,
) {
LoopAction::Continue => {} // fall through to step
LoopAction::Break => break,
LoopAction::Return(r) => return Ok(r),
}

// Execute step
Expand Down Expand Up @@ -2226,48 +2172,17 @@ impl Interpreter {
stdout.push_str(&result.stdout);
stderr.push_str(&result.stderr);
exit_code = result.exit_code;
last_errexit_suppressed = result.errexit_suppressed;

// Check for break/continue
match result.control_flow {
ControlFlow::Break(n) => {
if n <= 1 {
break;
} else {
return Ok(ExecResult {
stdout,
stderr,
exit_code,
control_flow: ControlFlow::Break(n - 1),
..Default::default()
});
}
}
ControlFlow::Continue(n) => {
if n <= 1 {
continue;
} else {
return Ok(ExecResult {
stdout,
stderr,
exit_code,
control_flow: ControlFlow::Continue(n - 1),
..Default::default()
});
}
}
ControlFlow::Return(code) => {
return Ok(ExecResult {
stdout,
stderr,
exit_code: code,
control_flow: ControlFlow::Return(code),
..Default::default()
});
}
ControlFlow::None => {
// errexit is already handled by execute_command_sequence_impl
}
match Self::handle_loop_control_flow(
&stdout,
&stderr,
exit_code,
&mut last_errexit_suppressed,
&result,
) {
LoopAction::Continue => {}
LoopAction::Break => break,
LoopAction::Return(r) => return Ok(r),
}
}

Expand Down Expand Up @@ -2786,6 +2701,58 @@ impl Interpreter {
self.execute_command_sequence_impl(commands, false).await
}

/// Handle control flow from a loop body iteration result.
///
/// Tracks `errexit_suppressed` from the result. Returns:
/// - `LoopAction::Continue` — proceed to next iteration
/// - `LoopAction::Break` — exit the loop normally
/// - `LoopAction::Return(ExecResult)` — propagate control flow (break N, continue N, return)
fn handle_loop_control_flow(
stdout: &str,
stderr: &str,
exit_code: i32,
last_errexit_suppressed: &mut bool,
result: &ExecResult,
) -> LoopAction {
*last_errexit_suppressed = result.errexit_suppressed;
match result.control_flow {
ControlFlow::Break(n) => {
if n <= 1 {
LoopAction::Break
} else {
LoopAction::Return(ExecResult {
stdout: stdout.to_string(),
stderr: stderr.to_string(),
exit_code,
control_flow: ControlFlow::Break(n - 1),
..Default::default()
})
}
}
ControlFlow::Continue(n) => {
if n <= 1 {
LoopAction::Continue
} else {
LoopAction::Return(ExecResult {
stdout: stdout.to_string(),
stderr: stderr.to_string(),
exit_code,
control_flow: ControlFlow::Continue(n - 1),
..Default::default()
})
}
}
ControlFlow::Return(code) => LoopAction::Return(ExecResult {
stdout: stdout.to_string(),
stderr: stderr.to_string(),
exit_code: code,
control_flow: ControlFlow::Return(code),
..Default::default()
}),
ControlFlow::None => LoopAction::Continue,
}
}

/// Execute a sequence of commands with optional errexit checking
async fn execute_command_sequence_impl(
&mut self,
Expand Down
Loading