feat: implement proper ("static") parameters handling command parser -> bot

feat: handle few more commands bot side
fix(commands): handle missing parameters and return error
refactor(commands): use ordermap instead of relying on a sort function to sort tokens
This commit is contained in:
dusk 2025-01-05 13:00:06 +09:00
parent 1a781014bd
commit eec9f64026
No known key found for this signature in database
16 changed files with 358 additions and 502 deletions

View file

@ -10,7 +10,8 @@ uniffi::include_scaffolding!("commands");
use core::panic;
use std::collections::HashMap;
use smol_str::SmolStr;
use ordermap::OrderMap;
use smol_str::{format_smolstr, SmolStr};
use tree::TreeBranch;
pub use commands::Command;
@ -21,27 +22,70 @@ lazy_static::lazy_static! {
let mut tree = TreeBranch {
current_command_key: None,
possible_tokens: vec![],
branches: HashMap::new(),
branches: OrderMap::new(),
};
crate::commands::all().iter().for_each(|x| tree.register_command(x.clone()));
tree.sort_tokens();
// println!("{{tree:#?}}");
crate::commands::all().into_iter().for_each(|x| tree.register_command(x));
tree
};
}
#[derive(Debug)]
pub enum CommandResult {
Ok { command: ParsedCommand },
Err { error: String },
}
#[derive(Debug)]
pub enum ParameterKind {
MemberRef,
SystemRef,
MemberPrivacyTarget,
PrivacyLevel,
OpaqueString,
}
#[derive(Debug)]
pub struct Parameter {
raw: String,
kind: ParameterKind,
}
impl Parameter {
fn new(raw: impl ToString, kind: ParameterKind) -> Self {
Self {
raw: raw.to_string(),
kind,
}
}
}
macro_rules! parameter_impl {
($($name:ident $kind:ident),*) => {
impl Parameter {
$(
fn $name(raw: impl ToString) -> Self {
Self::new(raw, $crate::ParameterKind::$kind)
}
)*
}
};
}
parameter_impl! {
opaque OpaqueString,
member MemberRef,
system SystemRef,
member_privacy_target MemberPrivacyTarget,
privacy_level PrivacyLevel
}
#[derive(Debug)]
pub struct ParsedCommand {
pub command_ref: String,
pub args: Vec<String>,
pub params: HashMap<String, Parameter>,
pub flags: HashMap<String, Option<String>>,
}
@ -53,9 +97,11 @@ fn parse_command(input: String) -> CommandResult {
let mut current_pos = 0;
let mut args: Vec<String> = Vec::new();
let mut params: HashMap<String, Parameter> = HashMap::new();
let mut flags: HashMap<String, Option<String>> = HashMap::new();
loop {
println!("{:?}", local_tree.possible_tokens);
let next = next_token(
local_tree.possible_tokens.clone(),
input.clone(),
@ -70,8 +116,22 @@ fn parse_command(input: String) -> CommandResult {
continue;
}
if let Some(arg) = arg {
args.push(arg.into());
if let Some(arg) = arg.as_ref() {
// get param name from token
// TODO: idk if this should be on token itself, doesn't feel right, but does work
let param = match &found_token {
Token::FullString(n) => Some((n, Parameter::opaque(arg))),
Token::MemberRef(n) => Some((n, Parameter::member(arg))),
Token::MemberPrivacyTarget(n) => Some((n, Parameter::member_privacy_target(arg))),
Token::SystemRef(n) => Some((n, Parameter::system(arg))),
Token::PrivacyLevel(n) => Some((n, Parameter::privacy_level(arg))),
_ => None,
};
// insert arg as paramater if this is a parameter
if let Some((param_name, param)) = param {
params.insert(param_name.to_string(), param);
}
args.push(arg.to_string());
}
if let Some(next_tree) = local_tree.branches.get(&found_token) {
@ -82,9 +142,11 @@ fn parse_command(input: String) -> CommandResult {
}
Err(None) => {
if let Some(command_ref) = local_tree.current_command_key {
println!("{command_ref} {params:?}");
return CommandResult::Ok {
command: ParsedCommand {
command_ref: command_ref.into(),
params,
args,
flags,
},
@ -136,19 +198,12 @@ fn next_token(
// iterate over tokens and run try_match
for token in possible_tokens {
if let TokenMatchResult::Match(value) =
// for FullString just send the whole string
token.try_match(if matches!(token, Token::FullString) {
if input.is_empty() {
None
} else {
Some(input.clone())
}
} else {
param.clone().map(|v| v.0)
})
{
return Ok((token, value, param.map(|v| v.1).unwrap_or(current_pos)));
// for FullString just send the whole string
let input_to_match = param.clone().map(|v| v.0);
match token.try_match(input_to_match) {
TokenMatchResult::Match(value) => return Ok((token, value, param.map(|v| v.1).unwrap_or(current_pos))),
TokenMatchResult::MissingParameter { name } => return Err(Some(format_smolstr!("Missing parameter `{name}` in command `{input} [{name}]`."))),
TokenMatchResult::NoMatch => {}
}
}