use core::fmt::Debug;
use core::str::FromStr;
use heapless::Vec;
use crate::lexer::{Flag, Token};
mod values;
pub use values::{AtMost, Values};
#[cfg(test)]
mod tests;
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum Error {
#[error("undefined argument")]
UndefinedArgument,
#[error("invalid argument")]
InvalidArgument,
#[error("no value expected")]
NoValueArgument,
#[error("missing argument")]
MissingArgument,
#[error("out of parser memory space")]
OutOfMemory,
}
pub type Result<T, E = Error> = core::result::Result<T, E>;
#[derive(Clone, Debug, PartialEq)]
pub enum Arg<'a> {
Named(&'a str, Values<'a>),
Positional(&'a str),
}
#[derive(Debug)]
pub struct ArgLookupTable<'a> {
table: &'a [(Flag<'a>, &'a str, AtMost)],
}
impl<'a> ArgLookupTable<'a> {
pub const fn new(table: &'a [(Flag<'a>, &'a str, AtMost)]) -> Self {
ArgLookupTable { table }
}
pub fn metadata_of(&self, flag: &Flag<'_>) -> Option<(&'a str, AtMost)> {
let (_, id, expected) = self.table.iter().find(|&x| x.0 == *flag)?;
Some((*id, *expected))
}
}
#[derive(Default, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct ParsedArgs<'a, const CAPACITY: usize = 1> {
args: Vec<Arg<'a>, CAPACITY>,
}
impl<'a, const CAPACITY: usize> ParsedArgs<'a, CAPACITY> {
pub fn parse_from(argv: &'a [&'a str], ids: &ArgLookupTable<'static>) -> Self {
Self::try_parse_from(argv, ids).expect("cannot parse arguments")
}
pub fn try_parse_from(
argv: &'a [&'a str],
table: &ArgLookupTable<'static>,
) -> Result<Self, Error> {
Self::check_capacity(argv)?;
Self::check_undefined_argument(argv, table)?;
let mut parsed = ParsedArgs::default();
let lookup = |flag: &Flag<'a>| {
unsafe { table.metadata_of(flag).unwrap_unchecked() }
};
let named = |args: &mut Vec<_, _>, name, expected, (start, end)| {
let (rest, arg) = Self::parse_arg_values(&argv[start..end], name, expected);
unsafe { args.push(arg).unwrap_unchecked() };
for value in rest.iter() {
unsafe { args.push(Arg::Positional(value)).unwrap_unchecked() };
}
};
let positional = |args: &mut Vec<_, _>, value| {
unsafe { args.push(Arg::Positional(value)).unwrap_unchecked() }
};
let parse_then_push =
|state, (index, arg): (usize, &&'a str)| match (state, Token::tokenize(arg)) {
(Some((flag, start)), Token::Flag(next)) => {
let (name, expected) = lookup(&flag);
named(&mut parsed.args, name, expected, (start, index));
Some((next, index + 1))
}
(Some(_), Token::Value(_)) => state,
(None, Token::Flag(flag)) => Some((flag, index + 1)),
(None, Token::Value(value)) => {
positional(&mut parsed.args, value);
None
}
};
let last_flag = argv.iter().enumerate().fold(None, parse_then_push);
if let Some((flag, start)) = last_flag {
let (name, expected) = lookup(&flag);
named(&mut parsed.args, name, expected, (start, argv.len()));
}
Ok(parsed)
}
#[inline(always)]
pub fn contains(&self, id: &str) -> bool {
self.args
.iter()
.any(|arg| matches!(arg, Arg::Named(name, _) if id == *name))
}
pub fn get_one<T>(&self, id: &str) -> Option<Option<T>>
where
T: FromStr,
{
self.try_get_one::<T>(id).expect("invalid argument")
}
pub fn get_many<B, T>(&self, id: &str) -> Option<B>
where
B: FromIterator<T>,
T: FromStr,
{
self.try_get_many::<B, T>(id).expect("invalid argument")
}
pub fn try_get_one<T>(&self, id: &str) -> Result<Option<Option<T>>, Error>
where
T: FromStr,
{
if let Some(Arg::Named(_, values)) = self
.args
.iter()
.find(|&x| matches!(x, Arg::Named(name, _) if *name == id))
{
let mut iter = values.iter();
let value = if let Some(value) = iter.next() {
value
} else {
return Ok(Some(None));
};
if iter.next().is_some() {
return Err(Error::InvalidArgument);
}
return value
.parse::<T>()
.map(Some)
.map(Some)
.map_err(|_| Error::InvalidArgument);
}
Ok(None)
}
pub fn try_get_many<B, T>(&self, id: &str) -> Result<Option<B>, Error>
where
B: FromIterator<T>,
T: FromStr,
{
if let Some(Arg::Named(_, values)) = self
.args
.iter()
.find(|&x| matches!(x, Arg::Named(name, _) if *name == id))
{
return Ok(Some(
values
.iter()
.map(|x| x.parse::<T>())
.collect::<Result<B, _>>()
.map_err(|_| Error::InvalidArgument)?,
));
}
Ok(None)
}
fn check_capacity(argv: &[&str]) -> Result<()> {
if CAPACITY < argv.len() {
return Err(Error::OutOfMemory);
}
Ok(())
}
fn check_undefined_argument(argv: &[&str], table: &ArgLookupTable<'_>) -> Result<()> {
let undefined = argv
.iter()
.map(|&x| Token::tokenize(x))
.any(|x| matches!(x, Token::Flag(flag) if table.metadata_of(&flag).is_none()));
if undefined {
return Err(Error::UndefinedArgument);
}
Ok(())
}
fn parse_arg_values<'b>(
argv: &'b [&'b str],
name: &'b str,
expected: AtMost,
) -> (Values<'b>, Arg<'b>) {
match expected {
AtMost::Zero => (Values::new(argv), Arg::Named(name, Values::empty())),
AtMost::One => {
let rest = if argv.len() <= 1 { &[] } else { &argv[1..] };
let arg = if argv.is_empty() { &[] } else { &argv[..1] };
(Values::new(rest), Arg::Named(name, Values::new(arg)))
}
AtMost::Many => (Values::empty(), Arg::Named(name, Values::new(argv))),
}
}
}