From b05129ad9fcd8218edcb760c8fe374bab0d8b8ab Mon Sep 17 00:00:00 2001 From: projectmoon Date: Tue, 25 May 2021 23:54:06 +0000 Subject: [PATCH] Localize all command parsing code into trait impls. This cleans up the command parser a lot, as all of the one or two line functions and associated imports have been removed. Unfortunately it does make the command files larger, as two trait impls are required: one for converting to Box, and one for converting from &str to the command type. Fixes #66. --- src/commands/basic_rolling.rs | 26 ++++++++ src/commands/cofd.rs | 25 ++++++++ src/commands/cthulhu.rs | 33 ++++++++++ src/commands/management.rs | 45 +++++++++++++- src/commands/misc.rs | 19 +++++- src/commands/parser.rs | 109 +++++++--------------------------- src/commands/variables.rs | 59 ++++++++++++++++++ 7 files changed, 228 insertions(+), 88 deletions(-) diff --git a/src/commands/basic_rolling.rs b/src/commands/basic_rolling.rs index ae438b5..163b1a8 100644 --- a/src/commands/basic_rolling.rs +++ b/src/commands/basic_rolling.rs @@ -1,11 +1,37 @@ use super::{Command, Execution, ExecutionResult}; use crate::basic::dice::ElementExpression; +use crate::basic::parser::parse_element_expression; use crate::basic::roll::Roll; use crate::context::Context; +use crate::error::BotError; use async_trait::async_trait; +use nom::Err as NomErr; +use std::convert::TryFrom; pub struct RollCommand(pub ElementExpression); +impl From for Box { + fn from(cmd: RollCommand) -> Self { + Box::new(cmd) + } +} + +impl TryFrom<&str> for RollCommand { + type Error = BotError; + + fn try_from(input: &str) -> Result { + let result = parse_element_expression(input); + match result { + Ok((rest, expression)) if rest.len() == 0 => Ok(RollCommand(expression)), + //"Legacy code boundary": translates Nom errors into BotErrors. + Ok(_) => Err(BotError::NomParserIncomplete), + Err(NomErr::Error(e)) => Err(BotError::NomParserError(e.1)), + Err(NomErr::Failure(e)) => Err(BotError::NomParserError(e.1)), + Err(NomErr::Incomplete(_)) => Err(BotError::NomParserIncomplete), + } + } +} + #[async_trait] impl Command for RollCommand { fn name(&self) -> &'static str { diff --git a/src/commands/cofd.rs b/src/commands/cofd.rs index 811b991..592f064 100644 --- a/src/commands/cofd.rs +++ b/src/commands/cofd.rs @@ -1,10 +1,35 @@ use super::{Command, Execution, ExecutionResult}; use crate::cofd::dice::{roll_pool, DicePool, DicePoolWithContext}; +use crate::cofd::parser::{create_chance_die, parse_dice_pool}; use crate::context::Context; +use crate::error::BotError; use async_trait::async_trait; +use std::convert::TryFrom; pub struct PoolRollCommand(pub DicePool); +impl PoolRollCommand { + pub fn chance_die() -> Result { + let pool = create_chance_die()?; + Ok(PoolRollCommand(pool)) + } +} + +impl From for Box { + fn from(cmd: PoolRollCommand) -> Self { + Box::new(cmd) + } +} + +impl TryFrom<&str> for PoolRollCommand { + type Error = BotError; + + fn try_from(input: &str) -> Result { + let pool = parse_dice_pool(input)?; + Ok(PoolRollCommand(pool)) + } +} + #[async_trait] impl Command for PoolRollCommand { fn name(&self) -> &'static str { diff --git a/src/commands/cthulhu.rs b/src/commands/cthulhu.rs index 45aa4da..9b69153 100644 --- a/src/commands/cthulhu.rs +++ b/src/commands/cthulhu.rs @@ -4,10 +4,28 @@ use crate::cthulhu::dice::{ advancement_roll, regular_roll, AdvancementRoll, AdvancementRollWithContext, DiceRoll, DiceRollWithContext, }; +use crate::cthulhu::parser::{parse_advancement_roll, parse_regular_roll}; +use crate::error::BotError; use async_trait::async_trait; +use std::convert::TryFrom; pub struct CthRoll(pub DiceRoll); +impl From for Box { + fn from(cmd: CthRoll) -> Self { + Box::new(cmd) + } +} + +impl TryFrom<&str> for CthRoll { + type Error = BotError; + + fn try_from(input: &str) -> Result { + let roll = parse_regular_roll(input)?; + Ok(CthRoll(roll)) + } +} + #[async_trait] impl Command for CthRoll { fn name(&self) -> &'static str { @@ -33,6 +51,21 @@ impl Command for CthRoll { pub struct CthAdvanceRoll(pub AdvancementRoll); +impl From for Box { + fn from(cmd: CthAdvanceRoll) -> Self { + Box::new(cmd) + } +} + +impl TryFrom<&str> for CthAdvanceRoll { + type Error = BotError; + + fn try_from(input: &str) -> Result { + let roll = parse_advancement_roll(input)?; + Ok(CthAdvanceRoll(roll)) + } +} + #[async_trait] impl Command for CthAdvanceRoll { fn name(&self) -> &'static str { diff --git a/src/commands/management.rs b/src/commands/management.rs index 6d4c68c..ab84029 100644 --- a/src/commands/management.rs +++ b/src/commands/management.rs @@ -1,13 +1,28 @@ use super::{Command, Execution, ExecutionResult}; -use crate::context::Context; use crate::db::Users; use crate::error::BotError::{AccountDoesNotExist, AuthenticationError, PasswordCreationError}; use crate::logic::hash_password; use crate::models::{AccountStatus, User}; +use crate::{context::Context, error::BotError}; use async_trait::async_trait; +use std::convert::{Into, TryFrom}; pub struct RegisterCommand(pub String); +impl From for Box { + fn from(cmd: RegisterCommand) -> Self { + Box::new(cmd) + } +} + +impl TryFrom<&str> for RegisterCommand { + type Error = BotError; + + fn try_from(value: &str) -> Result { + Ok(RegisterCommand(value.to_owned())) + } +} + #[async_trait] impl Command for RegisterCommand { fn name(&self) -> &'static str { @@ -38,6 +53,20 @@ impl Command for RegisterCommand { pub struct CheckCommand(pub String); +impl From for Box { + fn from(cmd: CheckCommand) -> Self { + Box::new(cmd) + } +} + +impl TryFrom<&str> for CheckCommand { + type Error = BotError; + + fn try_from(value: &str) -> Result { + Ok(CheckCommand(value.to_owned())) + } +} + #[async_trait] impl Command for CheckCommand { fn name(&self) -> &'static str { @@ -60,6 +89,20 @@ impl Command for CheckCommand { pub struct UnregisterCommand; +impl From for Box { + fn from(cmd: UnregisterCommand) -> Self { + Box::new(cmd) + } +} + +impl TryFrom<&str> for UnregisterCommand { + type Error = BotError; + + fn try_from(_: &str) -> Result { + Ok(UnregisterCommand) + } +} + #[async_trait] impl Command for UnregisterCommand { fn name(&self) -> &'static str { diff --git a/src/commands/misc.rs b/src/commands/misc.rs index 0b10d68..51b3a65 100644 --- a/src/commands/misc.rs +++ b/src/commands/misc.rs @@ -1,10 +1,27 @@ use super::{Command, Execution, ExecutionResult}; use crate::context::Context; -use crate::help::HelpTopic; +use crate::error::BotError; +use crate::help::{parse_help_topic, HelpTopic}; use async_trait::async_trait; +use std::convert::TryFrom; pub struct HelpCommand(pub Option); +impl From for Box { + fn from(cmd: HelpCommand) -> Self { + Box::new(cmd) + } +} + +impl TryFrom<&str> for HelpCommand { + type Error = BotError; + + fn try_from(input: &str) -> Result { + let topic = parse_help_topic(input); + Ok(HelpCommand(topic)) + } +} + #[async_trait] impl Command for HelpCommand { fn name(&self) -> &'static str { diff --git a/src/commands/parser.rs b/src/commands/parser.rs index 0f8567a..48ed9fa 100644 --- a/src/commands/parser.rs +++ b/src/commands/parser.rs @@ -3,8 +3,6 @@ * governed by the terms of the MIT license, from the original * axfive-matrix-dicebot project. */ -use crate::basic::parser::parse_element_expression; -use crate::cofd::parser::{create_chance_die, parse_dice_pool}; use crate::commands::{ basic_rolling::RollCommand, cofd::PoolRollCommand, @@ -16,13 +14,10 @@ use crate::commands::{ }, Command, }; -use crate::cthulhu::parser::{parse_advancement_roll, parse_regular_roll}; use crate::error::BotError; -use crate::help::parse_help_topic; -use crate::parser::variables::parse_set_variable; use combine::parser::char::{char, letter, space}; use combine::{any, many1, optional, Parser}; -use nom::Err as NomErr; +use std::convert::TryFrom; use thiserror::Error; #[derive(Debug, Clone, PartialEq, Error)] @@ -34,73 +29,6 @@ pub enum CommandParsingError { InternalParseError(#[from] combine::error::StringStreamError), } -// Parse a roll expression. -fn parse_roll(input: &str) -> Result, BotError> { - let result = parse_element_expression(input); - match result { - Ok((rest, expression)) if rest.len() == 0 => Ok(Box::new(RollCommand(expression))), - //Legacy code boundary translates nom errors into BotErrors. - Ok(_) => Err(BotError::NomParserIncomplete), - Err(NomErr::Error(e)) => Err(BotError::NomParserError(e.1)), - Err(NomErr::Failure(e)) => Err(BotError::NomParserError(e.1)), - Err(NomErr::Incomplete(_)) => Err(BotError::NomParserIncomplete), - } -} - -fn parse_register_command(input: &str) -> Result, BotError> { - Ok(Box::new(RegisterCommand(input.to_owned()))) -} - -fn parse_check_command(input: &str) -> Result, BotError> { - Ok(Box::new(CheckCommand(input.to_owned()))) -} - -fn parse_unregister_command() -> Result, BotError> { - Ok(Box::new(UnregisterCommand)) -} - -fn parse_get_variable_command(input: &str) -> Result, BotError> { - Ok(Box::new(GetVariableCommand(input.to_owned()))) -} - -fn parse_set_variable_command(input: &str) -> Result, BotError> { - let (variable_name, value) = parse_set_variable(input)?; - Ok(Box::new(SetVariableCommand(variable_name, value))) -} - -fn parse_delete_variable_command(input: &str) -> Result, BotError> { - Ok(Box::new(DeleteVariableCommand(input.to_owned()))) -} - -fn parse_pool_roll(input: &str) -> Result, BotError> { - let pool = parse_dice_pool(input)?; - Ok(Box::new(PoolRollCommand(pool))) -} - -fn parse_cth_roll(input: &str) -> Result, BotError> { - let roll = parse_regular_roll(input)?; - Ok(Box::new(CthRoll(roll))) -} - -fn parse_cth_advancement_roll(input: &str) -> Result, BotError> { - let roll = parse_advancement_roll(input)?; - Ok(Box::new(CthAdvanceRoll(roll))) -} - -fn chance_die() -> Result, BotError> { - let pool = create_chance_die()?; - Ok(Box::new(PoolRollCommand(pool))) -} - -fn get_all_variables() -> Result, BotError> { - Ok(Box::new(GetAllVariablesCommand)) -} - -fn help(topic: &str) -> Result, BotError> { - let topic = parse_help_topic(topic); - Ok(Box::new(HelpCommand(topic))) -} - /// Split an input string into its constituent command and "everything /// else" parts. Extracts the command separately from its input (i.e. /// rest of the line) and returns a tuple of (command_input, command). @@ -132,25 +60,34 @@ fn split_command(input: &str) -> Result<(String, String), CommandParsingError> { Ok((command, command_input)) } +/// Atempt to convert text input to a Boxed command type. Shortens +/// boilerplate. +macro_rules! convert_to { + ($type:ident, $input: expr) => { + $type::try_from($input.as_str()).map(Into::into) + }; +} + /// Potentially parse a command expression. If we recognize the /// command, an error should be raised if the command is misparsed. If /// we don't recognize the command, return an error. pub fn parse_command(input: &str) -> Result, BotError> { match split_command(input) { Ok((cmd, cmd_input)) => match cmd.to_lowercase().as_ref() { - "variables" => get_all_variables(), - "get" => parse_get_variable_command(&cmd_input), - "set" => parse_set_variable_command(&cmd_input), - "del" => parse_delete_variable_command(&cmd_input), - "r" | "roll" => parse_roll(&cmd_input), - "rp" | "pool" => parse_pool_roll(&cmd_input), - "cthroll" => parse_cth_roll(&cmd_input), - "cthadv" | "ctharoll" => parse_cth_advancement_roll(&cmd_input), - "chance" => chance_die(), - "help" => help(&cmd_input), - "register" => parse_register_command(&cmd_input), - "check" => parse_check_command(&cmd_input), - "unregister" => parse_unregister_command(), + // "variables" => GetAllVariablesCommand::try_from(input).map(Into::into), + "variables" => convert_to!(GetAllVariablesCommand, cmd_input), + "get" => convert_to!(GetVariableCommand, cmd_input), + "set" => convert_to!(SetVariableCommand, cmd_input), + "del" => convert_to!(DeleteVariableCommand, cmd_input), + "r" | "roll" => convert_to!(RollCommand, cmd_input), + "rp" | "pool" => convert_to!(PoolRollCommand, cmd_input), + "chance" => PoolRollCommand::chance_die().map(Into::into), + "cthroll" => convert_to!(CthRoll, cmd_input), + "cthadv" | "ctharoll" => convert_to!(CthAdvanceRoll, cmd_input), + "help" => convert_to!(HelpCommand, cmd_input), + "register" => convert_to!(RegisterCommand, cmd_input), + "check" => convert_to!(CheckCommand, cmd_input), + "unregister" => convert_to!(UnregisterCommand, cmd_input), _ => Err(CommandParsingError::UnrecognizedCommand(cmd).into()), }, //All other errors passed up. diff --git a/src/commands/variables.rs b/src/commands/variables.rs index fe7da5e..5971e5b 100644 --- a/src/commands/variables.rs +++ b/src/commands/variables.rs @@ -2,10 +2,26 @@ use super::{Command, Execution, ExecutionResult}; use crate::context::Context; use crate::db::errors::DataError; use crate::db::Variables; +use crate::error::BotError; use async_trait::async_trait; +use std::convert::TryFrom; pub struct GetAllVariablesCommand; +impl From for Box { + fn from(cmd: GetAllVariablesCommand) -> Self { + Box::new(cmd) + } +} + +impl TryFrom<&str> for GetAllVariablesCommand { + type Error = BotError; + + fn try_from(_: &str) -> Result { + Ok(GetAllVariablesCommand) + } +} + #[async_trait] impl Command for GetAllVariablesCommand { fn name(&self) -> &'static str { @@ -41,6 +57,20 @@ impl Command for GetAllVariablesCommand { pub struct GetVariableCommand(pub String); +impl From for Box { + fn from(cmd: GetVariableCommand) -> Self { + Box::new(cmd) + } +} + +impl TryFrom<&str> for GetVariableCommand { + type Error = BotError; + + fn try_from(input: &str) -> Result { + Ok(GetVariableCommand(input.to_owned())) + } +} + #[async_trait] impl Command for GetVariableCommand { fn name(&self) -> &'static str { @@ -71,6 +101,21 @@ impl Command for GetVariableCommand { pub struct SetVariableCommand(pub String, pub i32); +impl From for Box { + fn from(cmd: SetVariableCommand) -> Self { + Box::new(cmd) + } +} + +impl TryFrom<&str> for SetVariableCommand { + type Error = BotError; + + fn try_from(input: &str) -> Result { + let (variable_name, value) = crate::parser::variables::parse_set_variable(input)?; + Ok(SetVariableCommand(variable_name, value)) + } +} + #[async_trait] impl Command for SetVariableCommand { fn name(&self) -> &'static str { @@ -97,6 +142,20 @@ impl Command for SetVariableCommand { pub struct DeleteVariableCommand(pub String); +impl From for Box { + fn from(cmd: DeleteVariableCommand) -> Self { + Box::new(cmd) + } +} + +impl TryFrom<&str> for DeleteVariableCommand { + type Error = BotError; + + fn try_from(input: &str) -> Result { + Ok(DeleteVariableCommand(input.to_owned())) + } +} + #[async_trait] impl Command for DeleteVariableCommand { fn name(&self) -> &'static str {