From 649aec531e8d0ee89bb96714ee4a819cc1f7f8c0 Mon Sep 17 00:00:00 2001 From: "Taylor C. Richberger" Date: Tue, 21 Apr 2020 00:07:03 -0600 Subject: [PATCH] implement command parser fully --- src/bin/dicebot-cmd.rs | 12 ++-- src/commands.rs | 45 ++++++++++----- src/commands/parser.rs | 123 +++++++++-------------------------------- src/dice.rs | 42 ++++++++++++++ src/lib.rs | 1 + src/parser.rs | 7 ++- 6 files changed, 111 insertions(+), 119 deletions(-) diff --git a/src/bin/dicebot-cmd.rs b/src/bin/dicebot-cmd.rs index 6bdc5b3..5aeeb37 100644 --- a/src/bin/dicebot-cmd.rs +++ b/src/bin/dicebot-cmd.rs @@ -1,14 +1,16 @@ use axfive_matrix_dicebot::dice::parser::parse_element_expression; use axfive_matrix_dicebot::roll::{Roll, Rolled}; -use axfive_matrix_dicebot::commands::Command; +use axfive_matrix_dicebot::commands::parse_command; use std::error::Error; +use std::string::ToString; fn main() -> Result<(), String> { let command = std::env::args().skip(1).collect::>().join(" "); - let command: Command = match Command::parse(&command) { - Ok(command) => command.1, - Err(e) => return Err(format!("{}", e)), + let command = match parse_command(&command) { + Some(Ok(command)) => command, + Some(Err(e)) => return Err(format!("Error parsing command: {}", e)), + None => return Err("Command not recognized".into()), }; - println!("{}", command.execute()); + println!("{}", command.execute().plain()); Ok(()) } diff --git a/src/commands.rs b/src/commands.rs index 6e5626d..d7c397d 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -4,27 +4,44 @@ use nom::error::ErrorKind; use nom::IResult; pub mod parser; -pub struct RollCommand(ElementExpression); - -pub enum Command { - Roll(RollCommand), +pub struct Execution { + plain: String, + html: String, } -impl Command { - pub fn parse<'a>(input: &'a str) -> IResult<&'a str, Command> { - parser::parse_command(input) +impl Execution { + pub fn plain(&self) -> &str { + &self.plain } - // Type subject to change - pub fn execute(self) -> String { - match self { - Command::Roll(command) => command.execute(), + pub fn html(&self) -> &str { + &self.html + } +} + +pub struct RollCommand(ElementExpression); + +pub trait Command { + fn execute(&self) -> Execution; +} + +impl Command for RollCommand { + fn execute(&self) -> Execution { + let roll = self.0.roll(); + let plain = format!("Dice: {}\nResult: {}", self.0, roll); + let html = format!("Dice: {}
Result: {}", self.0, roll); + Execution { + plain, + html, } } } -impl RollCommand { - pub fn execute(self) -> String { - self.0.roll().to_string() +pub fn parse_command(s: &str) -> Option, String>> { + // Ignore trailing input, if any. + match parser::parse_command(s) { + Ok((_, Some(command))) => Some(Ok(command)), + Ok((_, None)) => None, + Err(err) => Some(Err(err.to_string())), } } diff --git a/src/commands/parser.rs b/src/commands/parser.rs index c314b72..24c4fee 100644 --- a/src/commands/parser.rs +++ b/src/commands/parser.rs @@ -1,7 +1,10 @@ use nom::{ - alt, + tuple, + switch, + take_while, bytes::complete::{tag, take_while}, character::complete::digit1, + character::is_alphabetic, complete, many0, named, sequence::tuple, tag, IResult, @@ -12,104 +15,30 @@ use crate::commands::{Command, RollCommand}; use crate::dice::parser::parse_element_expression; // Parse a roll expression. -fn parse_roll(input: &str) -> IResult<&str, Command> { - named!(invocation(&str) -> &str, alt!(complete!(tag!("!r")) | complete!(tag!("!roll")))); - let (input, _) = eat_whitespace(input)?; - let (input, _) = invocation(input)?; +fn parse_roll(input: &str) -> IResult<&str, RollCommand> { let (input, _) = eat_whitespace(input)?; let (input, expression) = parse_element_expression(input)?; - Ok((input, Command::Roll(RollCommand(expression)))) + Ok((input, RollCommand(expression))) } -// Parse a command expression. -pub fn parse_command(input: &str) -> IResult<&str, Command> { - // Add new commands to alt! - named!(command(&str) -> Command, alt!(parse_roll)); - command(input) +// 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, ignore it. +pub fn parse_command(original_input: &str) -> IResult<&str, Option>> { + let (input, _) = eat_whitespace(original_input)?; + named!(command(&str) -> (&str, &str), tuple!(complete!(tag!("!")), complete!(take_while!(char::is_alphabetic)))); + let (input, command) = match command(input) { + // Strip the exclamation mark + Ok((input, (_, result))) => (input, result), + Err(e) => return Ok((original_input, None)), + }; + let (input, command): (&str, Box) = match command { + "r" | "roll" => { + let (input, command) = parse_roll(input)?; + let command: Box = Box::new(command); + (input, command) + }, + // No recognized command, ignore this. + _ => return Ok((original_input, None)), + }; + Ok((input, Some(command))) } - -#[cfg(test)] -mod tests { - use super::*; - #[test] - fn dice_test() { - assert_eq!(parse_dice("2d4"), Ok(("", Dice::new(2, 4)))); - assert_eq!(parse_dice("20d40"), Ok(("", Dice::new(20, 40)))); - assert_eq!(parse_dice("8d7"), Ok(("", Dice::new(8, 7)))); - } - - #[test] - fn element_test() { - assert_eq!( - parse_element(" \t\n\r\n 8d7 \n"), - Ok((" \n", Element::Dice(Dice::new(8, 7)))) - ); - assert_eq!( - parse_element(" \t\n\r\n 8 \n"), - Ok((" \n", Element::Bonus(8))) - ); - } - - #[test] - fn signed_element_test() { - assert_eq!( - parse_signed_element("+ 7"), - Ok(("", SignedElement::Positive(Element::Bonus(7)))) - ); - assert_eq!( - parse_signed_element(" \t\n\r\n- 8 \n"), - Ok((" \n", SignedElement::Negative(Element::Bonus(8)))) - ); - assert_eq!( - parse_signed_element(" \t\n\r\n- 8d4 \n"), - Ok(( - " \n", - SignedElement::Negative(Element::Dice(Dice::new(8, 4))) - )) - ); - assert_eq!( - parse_signed_element(" \t\n\r\n+ 8d4 \n"), - Ok(( - " \n", - SignedElement::Positive(Element::Dice(Dice::new(8, 4))) - )) - ); - } - - #[test] - fn element_expression_test() { - assert_eq!( - parse_element_expression("8d4"), - Ok(( - "", - ElementExpression(vec![SignedElement::Positive(Element::Dice(Dice::new( - 8, 4 - )))]) - )) - ); - assert_eq!( - parse_element_expression(" - 8d4 \n "), - Ok(( - " \n ", - ElementExpression(vec![SignedElement::Negative(Element::Dice(Dice::new( - 8, 4 - )))]) - )) - ); - assert_eq!( - parse_element_expression("\t3d4 + 7 - 5 - 6d12 + 1d1 + 53 1d5 "), - Ok(( - " 1d5 ", - ElementExpression(vec![ - SignedElement::Positive(Element::Dice(Dice::new(3, 4))), - SignedElement::Positive(Element::Bonus(7)), - SignedElement::Negative(Element::Bonus(5)), - SignedElement::Negative(Element::Dice(Dice::new(6, 12))), - SignedElement::Positive(Element::Dice(Dice::new(1, 1))), - SignedElement::Positive(Element::Bonus(53)), - ]) - )) - ); - } -} - diff --git a/src/dice.rs b/src/dice.rs index 7fd0254..3c3efe6 100644 --- a/src/dice.rs +++ b/src/dice.rs @@ -1,5 +1,6 @@ pub mod parser; +use std::fmt; use std::ops::{Deref, DerefMut}; #[derive(Debug, PartialEq, Eq, Clone, Copy)] @@ -8,6 +9,12 @@ pub struct Dice { pub(crate) sides: u32, } +impl fmt::Display for Dice { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}d{}", self.count, self.sides) + } +} + impl Dice { fn new(count: u32, sides: u32) -> Dice { Dice { count, sides } @@ -20,12 +27,30 @@ pub enum Element { Bonus(u32), } +impl fmt::Display for Element { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Element::Dice(d) => write!(f, "{}", d), + Element::Bonus(b) => write!(f, "{}", b), + } + } +} + #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum SignedElement { Positive(Element), Negative(Element), } +impl fmt::Display for SignedElement { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + SignedElement::Positive(e) => write!(f, "{}", e), + SignedElement::Negative(e) => write!(f, "-{}", e), + } + } +} + #[derive(Debug, PartialEq, Eq, Clone)] pub struct ElementExpression(Vec); @@ -42,3 +67,20 @@ impl DerefMut for ElementExpression { &mut self.0 } } + +impl fmt::Display for ElementExpression { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut iter = self.0.iter(); + if let Some(first) = iter.next() { + write!(f, "{}", first)?; + for roll in iter { + match roll { + SignedElement::Positive(e) => write!(f, " + {}", e)?, + SignedElement::Negative(e) => write!(f, " - {}", e)?, + } + } + } + Ok(()) + } +} + diff --git a/src/lib.rs b/src/lib.rs index f92d8f6..beaf8dd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,3 +4,4 @@ pub mod matrix; pub mod roll; pub mod commands; mod parser; + diff --git a/src/parser.rs b/src/parser.rs index 0a11e17..e042978 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -7,7 +7,8 @@ fn is_whitespace(input: char) -> bool { input == ' ' || input == '\n' || input == '\t' || input == '\r' } -pub fn eat_whitespace(input: &str) -> IResult<&str, ()> { - let (input, _) = take_while(is_whitespace)(input)?; - Ok((input, ())) +/// Eat whitespace, returning it +pub fn eat_whitespace(input: &str) -> IResult<&str, &str> { + let (input, whitespace) = take_while(is_whitespace)(input)?; + Ok((input, whitespace)) }