use std::num::NonZeroUsize;
use std::ops::ControlFlow;
use thiserror::Error;
use yash_env::semantics::Divert;
use yash_env::semantics::ExitStatus;
use yash_env::stack::Stack;
#[derive(Clone, Debug, Eq, Error, PartialEq)]
#[non_exhaustive]
pub enum Error {
#[error("not in a loop")]
NotInLoop,
}
pub type Result = std::result::Result<crate::Result, Error>;
pub fn run(stack: &Stack, max_count: NonZeroUsize) -> Result {
let count = stack.loop_count(max_count.get());
if count == 0 {
return Err(Error::NotInLoop);
}
Ok(crate::Result::with_exit_status_and_divert(
ExitStatus::SUCCESS,
ControlFlow::Break(Divert::Break { count: count - 1 }),
))
}
#[cfg(test)]
mod tests {
use super::*;
use yash_env::semantics::Field;
use yash_env::stack::Builtin;
use yash_env::stack::Frame;
use yash_env::stack::StackFrameGuard;
fn push_break_builtin(stack: &mut Stack) -> StackFrameGuard<'_> {
stack.push(Frame::Builtin(Builtin {
name: Field::dummy("break"),
is_special: true,
}))
}
#[test]
fn count_fewer_than_loops() {
let mut stack = Stack::default();
let mut stack = push_break_builtin(&mut stack);
let mut stack = stack.push(Frame::Loop);
let stack = stack.push(Frame::Loop);
let count = NonZeroUsize::new(1).unwrap();
let result = run(&stack, count);
assert_eq!(
result,
Ok(crate::Result::with_exit_status_and_divert(
ExitStatus::SUCCESS,
ControlFlow::Break(Divert::Break { count: 0 }),
))
);
}
#[test]
fn count_equal_to_loops() {
let mut stack = Stack::default();
let mut stack = push_break_builtin(&mut stack);
let stack = stack.push(Frame::Loop);
let count = NonZeroUsize::new(1).unwrap();
let result = run(&stack, count);
assert_eq!(
result,
Ok(crate::Result::with_exit_status_and_divert(
ExitStatus::SUCCESS,
ControlFlow::Break(Divert::Break { count: 0 }),
))
);
}
#[test]
fn count_more_than_loops() {
let mut stack = Stack::default();
let mut stack = push_break_builtin(&mut stack);
let mut stack = stack.push(Frame::Loop);
let stack = stack.push(Frame::Loop);
let count = NonZeroUsize::new(3).unwrap();
let result = run(&stack, count);
assert_eq!(
result,
Ok(crate::Result::with_exit_status_and_divert(
ExitStatus::SUCCESS,
ControlFlow::Break(Divert::Break { count: 1 }),
))
);
}
#[test]
fn not_in_loop() {
let mut stack = Stack::default();
let stack = push_break_builtin(&mut stack);
let count = NonZeroUsize::new(1).unwrap();
let result = run(&stack, count);
assert_eq!(result, Err(Error::NotInLoop));
}
}