use std::fmt::{Debug, Display}; use smol_str::SmolStr; use crate::parameter::{Parameter, ParameterValue}; #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Token { /// A bot-defined command / subcommand (usually) (eg. "member" in `pk;member MyName`) Value { name: SmolStr, aliases: Vec, }, /// A parameter that must be provided a value Parameter(Parameter), } #[derive(Clone, Debug)] pub enum TokenMatchResult { MatchedValue, MatchedParameter { name: SmolStr, value: ParameterValue, }, ParameterMatchError { input: SmolStr, msg: SmolStr, }, MissingParameter { name: SmolStr, }, } // q: why not have a NoMatch variant in TokenMatchResult? // a: because we want to differentiate between no match and match failure (it matched with an error) // "no match" has a different charecteristic because we want to continue matching other tokens... // ...while "match failure" means we should stop matching and return the error // Option fits this better (and it makes some code look a bit nicer) pub type TryMatchResult = Option; impl Token { pub fn try_match(&self, input: Option<&str>) -> TryMatchResult { let input = match input { Some(input) => input, None => { // short circuit on: return match self { // missing paramaters Self::Parameter(param) => Some( param .is_optional() .then(|| TokenMatchResult::MatchedParameter { name: param.name().into(), value: ParameterValue::Null, }) .unwrap_or_else(|| TokenMatchResult::MissingParameter { name: param.name().into(), }), ), // everything else doesnt match if no input anyway Self::Value { .. } => None, // don't add a _ match here! }; } }; let input = input.trim(); // try actually matching stuff match self { Self::Value { name, aliases } => (aliases.iter().chain(std::iter::once(name))) .any(|v| v.eq(input)) .then(|| TokenMatchResult::MatchedValue), Self::Parameter(param) => Some(match param.match_value(input) { Ok(matched) => TokenMatchResult::MatchedParameter { name: param.name().into(), value: matched, }, Err(err) => { if param.is_skip() { return None; } else { TokenMatchResult::ParameterMatchError { input: input.into(), msg: err, } } } }), // don't add a _ match here! } } } impl Display for Token { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Value { name, .. } => write!(f, "{name}"), Self::Parameter(param) => write!(f, "{param}"), } } } // (name, aliases) -> Token::Value impl From<(&str, [&str; L])> for Token { fn from((name, aliases): (&str, [&str; L])) -> Self { Self::Value { name: name.into(), aliases: aliases.into_iter().map(SmolStr::new).collect::>(), } } } // name -> Token::Value impl From<&str> for Token { fn from(value: &str) -> Self { Self::from((value, [])) } } // parameter -> Token::Parameter impl> From

for Token { fn from(value: P) -> Self { Self::Parameter(value.into()) } } /// Iterator that produces [`Token`]s. /// /// This is more of a convenience type that the [`tokens!`] macro uses in order /// to more easily combine tokens together. #[derive(Debug, Clone)] pub struct TokensIterator { inner: Vec, } impl TokensIterator { pub(crate) fn new(tokens: Vec) -> Self { Self { inner: tokens } } } impl Iterator for TokensIterator { type Item = Token; fn next(&mut self) -> Option { (self.inner.len() > 0).then(|| self.inner.remove(0)) } } impl From> for TokensIterator { fn from(value: Vec) -> Self { Self::new(value) } } impl> From for TokensIterator { fn from(value: T) -> Self { Self::new(vec![value.into()]) } } impl From<[Token; L]> for TokensIterator { fn from(value: [Token; L]) -> Self { Self::new(value.into_iter().collect()) } } impl From<[Self; L]> for TokensIterator { fn from(value: [Self; L]) -> Self { Self::new(value.into_iter().map(|t| t.inner).flatten().collect()) } } #[macro_export] macro_rules! tokens { ($($v:expr),+$(,)*) => { $crate::token::TokensIterator::from([$($crate::token::TokensIterator::from($v.clone())),+]) }; }