Skip to main content

noshell_parser/
parser.rs

1//! A parser for collecting arguments from a token stream.
2
3use core::fmt::Debug;
4use core::str::FromStr;
5
6use heapless::Vec;
7
8use crate::lexer::{Flag, Token};
9
10mod values;
11
12pub use values::{AtMost, Values};
13
14#[cfg(test)]
15mod tests;
16
17/// Defines the possible errors that may occur during parsing of arguments.
18#[derive(Debug, PartialEq, Eq, thiserror::Error)]
19#[cfg_attr(feature = "defmt", derive(defmt::Format))]
20#[non_exhaustive]
21pub enum Error {
22    /// The argument is not defined.
23    #[error("undefined argument")]
24    UndefinedArgument,
25
26    /// The argument value is invalid, meaning that it cannot be converted to the destination
27    /// type. This could mean that there is a missing implementation for [`str::parse`] trait.
28    #[error("invalid argument")]
29    InvalidArgument,
30
31    /// The argument has no expected value on the command line.
32    #[error("no value expected")]
33    NoValueArgument,
34
35    /// The argument value is missing, which occurs when the flag is not boolean and expect a
36    /// value.
37    #[error("missing argument")]
38    MissingArgument,
39
40    /// Insufficient space for parsing arguments.
41    #[error("out of parser memory space")]
42    OutOfMemory,
43}
44
45/// Re-export of result type with module [`Error`].
46pub type Result<T, E = Error> = core::result::Result<T, E>;
47
48/// Defines an argument on the command line.
49#[derive(Clone, Debug, PartialEq)]
50pub enum Arg<'a> {
51    /// A named argument, which is defined by a flag, and zero or more values.
52    Named(&'a str, Values<'a>),
53
54    /// A positional argument, which is defined by its value.
55    Positional(&'a str),
56}
57
58/// Argument id to metadata look-up table.
59#[derive(Debug)]
60pub struct ArgLookupTable<'a> {
61    table: &'a [(Flag<'a>, &'a str, AtMost)],
62}
63
64impl<'a> ArgLookupTable<'a> {
65    /// Create a new look-up table.
66    pub const fn new(table: &'a [(Flag<'a>, &'a str, AtMost)]) -> Self {
67        ArgLookupTable { table }
68    }
69
70    /// Look up for a flag.
71    pub fn metadata_of(&self, flag: &Flag<'_>) -> Option<(&'a str, AtMost)> {
72        let (_, id, expected) = self.table.iter().find(|&x| x.0 == *flag)?;
73        Some((*id, *expected))
74    }
75}
76
77/// Defines the result of argument parsing. This is a simple key-value store that offers a look-up
78/// over parsed arguments.
79#[derive(Default, Debug)]
80#[cfg_attr(feature = "defmt", derive(defmt::Format))]
81pub struct ParsedArgs<'a, const CAPACITY: usize = 1> {
82    args: Vec<Arg<'a>, CAPACITY>,
83}
84
85impl<'a, const CAPACITY: usize> ParsedArgs<'a, CAPACITY> {
86    /// Parse the command line input from a token stream. The result is the set of found arguments.
87    pub fn parse_from(argv: &'a [&'a str], ids: &ArgLookupTable<'static>) -> Self {
88        Self::try_parse_from(argv, ids).expect("cannot parse arguments")
89    }
90
91    /// Try to parse the input arguments.
92    pub fn try_parse_from(
93        argv: &'a [&'a str],
94        table: &ArgLookupTable<'static>,
95    ) -> Result<Self, Error> {
96        // Some initial checks before start parsing.
97        Self::check_capacity(argv)?;
98        Self::check_undefined_argument(argv, table)?;
99
100        let mut parsed = ParsedArgs::default();
101
102        let lookup = |flag: &Flag<'a>| {
103            // SAFETY: the validation above guarantees that the lookup found an entry.
104            unsafe { table.metadata_of(flag).unwrap_unchecked() }
105        };
106
107        let named = |args: &mut Vec<_, _>, name, expected, (start, end)| {
108            let (rest, arg) = Self::parse_arg_values(&argv[start..end], name, expected);
109
110            // SAFETY: the validation above guarantees that the capacity of the resulting
111            // parsed args is sufficient.
112            unsafe { args.push(arg).unwrap_unchecked() };
113
114            for value in rest.iter() {
115                // SAFETY: the validation above guarantees that the capacity of the resulting
116                // parsed args is sufficient.
117                unsafe { args.push(Arg::Positional(value)).unwrap_unchecked() };
118            }
119        };
120
121        let positional = |args: &mut Vec<_, _>, value| {
122            // SAFETY: the validation above guarantees that the capacity of the resulting
123            // parsed args is sufficient.
124            unsafe { args.push(Arg::Positional(value)).unwrap_unchecked() }
125        };
126
127        let parse_then_push =
128            |state, (index, arg): (usize, &&'a str)| match (state, Token::tokenize(arg)) {
129                // A flag has been met, while this new flag occurs, then save the previous one and
130                // keep going on the new flag values.
131                (Some((flag, start)), Token::Flag(next)) => {
132                    let (name, expected) = lookup(&flag);
133                    named(&mut parsed.args, name, expected, (start, index));
134                    Some((next, index + 1))
135                }
136
137                // A flag has been met and this value belong to it, then keep going.
138                (Some(_), Token::Value(_)) => state,
139
140                // No flag has been met and a new one occurs, then keep going on the new flag
141                // values.
142                (None, Token::Flag(flag)) => Some((flag, index + 1)),
143
144                // No flag has been met, then this value is a positional argument.
145                (None, Token::Value(value)) => {
146                    positional(&mut parsed.args, value);
147                    None
148                }
149            };
150
151        let last_flag = argv.iter().enumerate().fold(None, parse_then_push);
152
153        if let Some((flag, start)) = last_flag {
154            let (name, expected) = lookup(&flag);
155            named(&mut parsed.args, name, expected, (start, argv.len()));
156        }
157
158        Ok(parsed)
159    }
160
161    /// Check if there exists an argument with the given key (i.e. short or long flag).
162    #[inline(always)]
163    pub fn contains(&self, id: &str) -> bool {
164        self.args
165            .iter()
166            .any(|arg| matches!(arg, Arg::Named(name, _) if id == *name))
167    }
168
169    /// Get one value for the given flag identifier.
170    pub fn get_one<T>(&self, id: &str) -> Option<Option<T>>
171    where
172        T: FromStr,
173    {
174        self.try_get_one::<T>(id).expect("invalid argument")
175    }
176
177    /// Get many values for the given flag identifier.
178    pub fn get_many<B, T>(&self, id: &str) -> Option<B>
179    where
180        B: FromIterator<T>,
181        T: FromStr,
182    {
183        self.try_get_many::<B, T>(id).expect("invalid argument")
184    }
185
186    /// Try to get and parse the argument value if any.
187    pub fn try_get_one<T>(&self, id: &str) -> Result<Option<Option<T>>, Error>
188    where
189        T: FromStr,
190    {
191        if let Some(Arg::Named(_, values)) = self
192            .args
193            .iter()
194            .find(|&x| matches!(x, Arg::Named(name, _) if *name == id))
195        {
196            let mut iter = values.iter();
197
198            let value = if let Some(value) = iter.next() {
199                value
200            } else {
201                // The argument has no value.
202                return Ok(Some(None));
203            };
204
205            if iter.next().is_some() {
206                // The argument has more than one value.
207                return Err(Error::InvalidArgument);
208            }
209
210            return value
211                .parse::<T>()
212                // The argument is present and has a value (i.e. Some(Some(_))).
213                .map(Some)
214                .map(Some)
215                // The value cannot be parsed to the target type `T`.
216                .map_err(|_| Error::InvalidArgument);
217        }
218
219        // The argument has not been found.
220        Ok(None)
221    }
222
223    /// Try to get and parse the argument value if any. The value can be constructed from
224    /// an iterator.
225    pub fn try_get_many<B, T>(&self, id: &str) -> Result<Option<B>, Error>
226    where
227        B: FromIterator<T>,
228        T: FromStr,
229    {
230        if let Some(Arg::Named(_, values)) = self
231            .args
232            .iter()
233            .find(|&x| matches!(x, Arg::Named(name, _) if *name == id))
234        {
235            return Ok(Some(
236                values
237                    .iter()
238                    .map(|x| x.parse::<T>())
239                    .collect::<Result<B, _>>()
240                    .map_err(|_| Error::InvalidArgument)?,
241            ));
242        }
243
244        Ok(None)
245    }
246
247    fn check_capacity(argv: &[&str]) -> Result<()> {
248        if CAPACITY < argv.len() {
249            return Err(Error::OutOfMemory);
250        }
251        Ok(())
252    }
253
254    fn check_undefined_argument(argv: &[&str], table: &ArgLookupTable<'_>) -> Result<()> {
255        let undefined = argv
256            .iter()
257            .map(|&x| Token::tokenize(x))
258            .any(|x| matches!(x, Token::Flag(flag) if table.metadata_of(&flag).is_none()));
259
260        if undefined {
261            return Err(Error::UndefinedArgument);
262        }
263
264        Ok(())
265    }
266
267    fn parse_arg_values<'b>(
268        argv: &'b [&'b str],
269        name: &'b str,
270        expected: AtMost,
271    ) -> (Values<'b>, Arg<'b>) {
272        match expected {
273            AtMost::Zero => (Values::new(argv), Arg::Named(name, Values::empty())),
274            AtMost::One => {
275                let rest = if argv.len() <= 1 { &[] } else { &argv[1..] };
276                let arg = if argv.is_empty() { &[] } else { &argv[..1] };
277                (Values::new(rest), Arg::Named(name, Values::new(arg)))
278            }
279            AtMost::Many => (Values::empty(), Arg::Named(name, Values::new(argv))),
280        }
281    }
282}