PluralKit/crates/command_parser/src/token.rs

145 lines
4.4 KiB
Rust
Raw Normal View History

use std::{
fmt::{Debug, Display},
ops::Not,
};
use smol_str::SmolStr;
use crate::parameter::{Parameter, ParameterKind, ParameterValue};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2024-09-13 16:02:30 +09:00
pub enum Token {
/// Token used to represent a finished command (i.e. no more parameters required)
// todo: this is likely not the right way to represent this
Empty,
/// A bot-defined command / subcommand (usually) (eg. "member" in `pk;member MyName`)
Value(Vec<SmolStr>),
2024-09-13 16:02:30 +09:00
/// A parameter that must be provided a value
Parameter(Parameter),
2024-09-13 16:02:30 +09:00
}
#[derive(Debug)]
pub enum TokenMatchError {
ParameterMatchError { input: SmolStr, msg: SmolStr },
MissingParameter { name: SmolStr },
}
#[derive(Debug)]
pub(super) struct TokenMatchValue {
pub raw: SmolStr,
pub param: Option<(SmolStr, ParameterValue)>,
2024-09-13 16:02:30 +09:00
}
impl TokenMatchValue {
fn new_match(raw: impl Into<SmolStr>) -> TryMatchResult {
Some(Ok(Some(Self {
raw: raw.into(),
param: None,
})))
}
fn new_match_param(
raw: impl Into<SmolStr>,
param_name: impl Into<SmolStr>,
param: ParameterValue,
) -> TryMatchResult {
Some(Ok(Some(Self {
raw: raw.into(),
param: Some((param_name.into(), param)),
})))
}
}
2024-09-13 16:02:30 +09:00
/// None -> no match
/// Some(Ok(None)) -> match, no value
/// Some(Ok(Some(_))) -> match, with value
/// Some(Err(_)) -> error while matching
// q: why do this while we could have a NoMatch in TokenMatchError?
// 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
type TryMatchResult = Option<Result<Option<TokenMatchValue>, TokenMatchError>>;
2024-09-13 16:02:30 +09:00
impl Token {
pub(super) fn try_match(&self, input: Option<&str>) -> TryMatchResult {
let input = match input {
Some(input) => input,
None => {
// short circuit on:
return match self {
// empty token
Self::Empty => Some(Ok(None)),
// missing paramaters
Self::Parameter(param) => Some(Err(TokenMatchError::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();
2024-09-13 16:02:30 +09:00
// try actually matching stuff
match self {
Self::Empty => None,
Self::Value(values) => values
.iter()
.any(|v| v.eq(input))
.then(|| TokenMatchValue::new_match(input))
.unwrap_or(None),
Self::Parameter(param) => match param.kind().match_value(input) {
Ok(matched) => TokenMatchValue::new_match_param(input, param.name(), matched),
Err(err) => Some(Err(TokenMatchError::ParameterMatchError {
input: input.into(),
msg: err,
})),
}, // don't add a _ match here!
}
2024-09-13 16:02:30 +09:00
}
}
impl Display for Token {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Token::Empty => write!(f, ""),
Token::Value(vec) if vec.is_empty().not() => write!(f, "{}", vec.first().unwrap()),
Token::Value(_) => Ok(()), // if value token has no values (lol), don't print anything
Token::Parameter(param) => param.kind().format(f, param.name()),
}
}
}
impl From<&str> for Token {
fn from(value: &str) -> Self {
Token::Value(vec![SmolStr::new(value)])
}
}
impl<const L: usize> From<[&str; L]> for Token {
fn from(value: [&str; L]) -> Self {
Token::Value(value.into_iter().map(SmolStr::from).collect::<Vec<_>>())
}
}
impl From<Parameter> for Token {
fn from(value: Parameter) -> Self {
Token::Parameter(value)
}
}
impl From<ParameterKind> for Token {
fn from(value: ParameterKind) -> Self {
Token::from(Parameter::from(value))
}
}
impl From<(&str, ParameterKind)> for Token {
fn from(value: (&str, ParameterKind)) -> Self {
Token::from(Parameter::from(value))
}
}