yash_builtin/break/
semantics.rs1use std::num::NonZeroUsize;
22use std::ops::ControlFlow;
23use thiserror::Error;
24use yash_env::semantics::Divert;
25use yash_env::semantics::ExitStatus;
26use yash_env::stack::Stack;
27
28#[derive(Clone, Debug, Eq, Error, PartialEq)]
30#[non_exhaustive]
31pub enum Error {
32 #[error("not in a loop")]
34 NotInLoop,
35}
36
37pub type Result = std::result::Result<crate::Result, Error>;
39
40pub fn run(stack: &Stack, max_count: NonZeroUsize) -> Result {
42 let count = stack.loop_count(max_count.get());
43 if count == 0 {
44 return Err(Error::NotInLoop);
45 }
46
47 Ok(crate::Result::with_exit_status_and_divert(
48 ExitStatus::SUCCESS,
49 ControlFlow::Break(Divert::Break { count: count - 1 }),
50 ))
51}
52
53#[cfg(test)]
54mod tests {
55 use super::*;
56 use yash_env::semantics::Field;
57 use yash_env::stack::Builtin;
58 use yash_env::stack::Frame;
59 use yash_env::stack::StackFrameGuard;
60
61 fn push_break_builtin(stack: &mut Stack) -> StackFrameGuard<'_> {
62 stack.push(Frame::Builtin(Builtin {
63 name: Field::dummy("break"),
64 is_special: true,
65 }))
66 }
67
68 #[test]
69 fn count_fewer_than_loops() {
70 let mut stack = Stack::default();
71 let mut stack = push_break_builtin(&mut stack);
72 let mut stack = stack.push(Frame::Loop);
73 let stack = stack.push(Frame::Loop);
74 let count = NonZeroUsize::new(1).unwrap();
75
76 let result = run(&stack, count);
77 assert_eq!(
78 result,
79 Ok(crate::Result::with_exit_status_and_divert(
80 ExitStatus::SUCCESS,
81 ControlFlow::Break(Divert::Break { count: 0 }),
82 ))
83 );
84 }
85
86 #[test]
87 fn count_equal_to_loops() {
88 let mut stack = Stack::default();
89 let mut stack = push_break_builtin(&mut stack);
90 let stack = stack.push(Frame::Loop);
91 let count = NonZeroUsize::new(1).unwrap();
92
93 let result = run(&stack, count);
94 assert_eq!(
95 result,
96 Ok(crate::Result::with_exit_status_and_divert(
97 ExitStatus::SUCCESS,
98 ControlFlow::Break(Divert::Break { count: 0 }),
99 ))
100 );
101 }
102
103 #[test]
104 fn count_more_than_loops() {
105 let mut stack = Stack::default();
106 let mut stack = push_break_builtin(&mut stack);
107 let mut stack = stack.push(Frame::Loop);
108 let stack = stack.push(Frame::Loop);
109 let count = NonZeroUsize::new(3).unwrap();
110
111 let result = run(&stack, count);
112 assert_eq!(
113 result,
114 Ok(crate::Result::with_exit_status_and_divert(
115 ExitStatus::SUCCESS,
116 ControlFlow::Break(Divert::Break { count: 1 }),
117 ))
118 );
119 }
120
121 #[test]
122 fn not_in_loop() {
123 let mut stack = Stack::default();
124 let stack = push_break_builtin(&mut stack);
125 let count = NonZeroUsize::new(1).unwrap();
126 let result = run(&stack, count);
127 assert_eq!(result, Err(Error::NotInLoop));
128 }
129}