forked from projectmoon/tenebrous-dicebot
implement command parser fully
This commit is contained in:
parent
90ae9c142c
commit
649aec531e
|
@ -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::<Vec<String>>().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(())
|
||||
}
|
||||
|
|
|
@ -4,27 +4,44 @@ use nom::error::ErrorKind;
|
|||
use nom::IResult;
|
||||
pub mod parser;
|
||||
|
||||
pub struct Execution {
|
||||
plain: String,
|
||||
html: String,
|
||||
}
|
||||
|
||||
impl Execution {
|
||||
pub fn plain(&self) -> &str {
|
||||
&self.plain
|
||||
}
|
||||
|
||||
pub fn html(&self) -> &str {
|
||||
&self.html
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RollCommand(ElementExpression);
|
||||
|
||||
pub enum Command {
|
||||
Roll(RollCommand),
|
||||
pub trait Command {
|
||||
fn execute(&self) -> Execution;
|
||||
}
|
||||
|
||||
impl Command {
|
||||
pub fn parse<'a>(input: &'a str) -> IResult<&'a str, Command> {
|
||||
parser::parse_command(input)
|
||||
}
|
||||
|
||||
// Type subject to change
|
||||
pub fn execute(self) -> String {
|
||||
match self {
|
||||
Command::Roll(command) => command.execute(),
|
||||
impl Command for RollCommand {
|
||||
fn execute(&self) -> Execution {
|
||||
let roll = self.0.roll();
|
||||
let plain = format!("Dice: {}\nResult: {}", self.0, roll);
|
||||
let html = format!("<strong>Dice:</strong> {}<br><strong>Result</strong>: {}", 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<Result<Box<dyn Command>, 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())),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Box<dyn Command>>> {
|
||||
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<dyn Command>) = match command {
|
||||
"r" | "roll" => {
|
||||
let (input, command) = parse_roll(input)?;
|
||||
let command: Box<dyn Command> = 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)),
|
||||
])
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
42
src/dice.rs
42
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<SignedElement>);
|
||||
|
||||
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,3 +4,4 @@ pub mod matrix;
|
|||
pub mod roll;
|
||||
pub mod commands;
|
||||
mod parser;
|
||||
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue