Skip to main content

yash_builtin/break/
semantics.rs

1// This file is part of yash, an extended POSIX shell.
2// Copyright (C) 2023 WATANABE Yuki
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program.  If not, see <https://www.gnu.org/licenses/>.
16
17//! Main semantics of the break built-in
18//!
19//! Some items in this module are shared with the continue built-in.
20
21use 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/// Error in running the break/continue built-in
29#[derive(Clone, Debug, Eq, Error, PartialEq)]
30#[non_exhaustive]
31pub enum Error {
32    /// There is no lexically enclosing loop.
33    #[error("not in a loop")]
34    NotInLoop,
35}
36
37/// Result of running the break/continue built-in
38pub type Result = std::result::Result<crate::Result, Error>;
39
40/// Computes the result of the break built-in.
41pub 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}