Skip to main content

yash_builtin/break/
syntax.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//! Command line argument parser for the break/continue built-in
18
19use crate::common::syntax::Mode;
20use crate::common::syntax::parse_arguments;
21use std::num::NonZeroUsize;
22use std::num::ParseIntError;
23use thiserror::Error;
24use yash_env::Env;
25use yash_env::semantics::Field;
26use yash_env::source::pretty::Report;
27use yash_env::source::pretty::ReportType;
28use yash_env::source::pretty::Snippet;
29
30/// Error in parsing command line arguments
31#[derive(Clone, Debug, Eq, Error, PartialEq)]
32#[non_exhaustive]
33pub enum Error {
34    /// An error occurred in the common parser.
35    #[error(transparent)]
36    CommonError(#[from] crate::common::syntax::ParseError<'static>),
37
38    /// More than one operand is given.
39    #[error("too many operands")]
40    TooManyOperands(Vec<Field>),
41
42    /// The operand is not a valid positive integer.
43    #[error("invalid numeric operand")]
44    InvalidNumber(Field, ParseIntError),
45}
46
47impl Error {
48    /// Converts this error to a [`Report`].
49    #[must_use]
50    pub fn to_report(&self) -> Report<'_> {
51        let (location, label) = match self {
52            Self::CommonError(e) => return e.to_report(),
53            Self::TooManyOperands(operands) => (
54                &operands[1].origin,
55                format!("{}: redundant operand", operands[1].value),
56            ),
57            Self::InvalidNumber(operand, e) => {
58                (&operand.origin, format!("{}: {}", operand.value, e))
59            }
60        };
61
62        let mut report = Report::new();
63        report.r#type = ReportType::Error;
64        report.title = self.to_string().into();
65        report.snippets = Snippet::with_primary_span(location, label.into());
66        report
67    }
68}
69
70impl<'a> From<&'a Error> for Report<'a> {
71    #[inline]
72    fn from(error: &'a Error) -> Self {
73        error.to_report()
74    }
75}
76
77/// Result of parsing command line arguments
78///
79/// If successful, the result is the number of levels to break.
80pub type Result = std::result::Result<NonZeroUsize, Error>;
81
82/// Parses command line arguments for the break/continue built-in.
83pub fn parse<S>(env: &Env<S>, args: Vec<Field>) -> Result {
84    let (_options, mut operands) = parse_arguments(&[], Mode::with_env(env), args)?;
85
86    if operands.len() > 1 {
87        return Err(Error::TooManyOperands(operands));
88    }
89
90    match operands.pop() {
91        None => Ok(NonZeroUsize::new(1).unwrap()),
92
93        Some(field) => field
94            .value
95            .parse()
96            .map_err(|e| Error::InvalidNumber(field, e)),
97    }
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103    use assert_matches::assert_matches;
104    use std::num::IntErrorKind;
105
106    #[test]
107    fn default_count() {
108        let env = Env::new_virtual();
109        let result = parse(&env, vec![]);
110        assert_eq!(result, Ok(NonZeroUsize::new(1).unwrap()));
111    }
112
113    #[test]
114    fn valid_counts() {
115        let env = Env::new_virtual();
116        let args = Field::dummies(["1"]);
117        let result = parse(&env, args);
118        assert_eq!(result, Ok(NonZeroUsize::new(1).unwrap()));
119
120        let args = Field::dummies(["2"]);
121        let result = parse(&env, args);
122        assert_eq!(result, Ok(NonZeroUsize::new(2).unwrap()));
123    }
124
125    #[test]
126    fn too_many_operands() {
127        let env = Env::new_virtual();
128        let args = Field::dummies(["1", "2"]);
129        let result = parse(&env, args.clone());
130        assert_eq!(result, Err(Error::TooManyOperands(args)));
131    }
132
133    #[test]
134    fn non_positive_integer() {
135        let env = Env::new_virtual();
136        let arg = Field::dummy("0");
137        let result = parse(&env, vec![arg.clone()]);
138        assert_matches!(result, Err(Error::InvalidNumber(field, error)) => {
139            assert_eq!(field, arg);
140            assert_eq!(error.kind(), &IntErrorKind::Zero);
141        });
142    }
143}