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::dice::parser::parse_element_expression;
|
||||||
use axfive_matrix_dicebot::roll::{Roll, Rolled};
|
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::error::Error;
|
||||||
|
use std::string::ToString;
|
||||||
|
|
||||||
fn main() -> Result<(), String> {
|
fn main() -> Result<(), String> {
|
||||||
let command = std::env::args().skip(1).collect::<Vec<String>>().join(" ");
|
let command = std::env::args().skip(1).collect::<Vec<String>>().join(" ");
|
||||||
let command: Command = match Command::parse(&command) {
|
let command = match parse_command(&command) {
|
||||||
Ok(command) => command.1,
|
Some(Ok(command)) => command,
|
||||||
Err(e) => return Err(format!("{}", e)),
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,27 +4,44 @@ use nom::error::ErrorKind;
|
||||||
use nom::IResult;
|
use nom::IResult;
|
||||||
pub mod parser;
|
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 struct RollCommand(ElementExpression);
|
||||||
|
|
||||||
pub enum Command {
|
pub trait Command {
|
||||||
Roll(RollCommand),
|
fn execute(&self) -> Execution;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Command {
|
impl Command for RollCommand {
|
||||||
pub fn parse<'a>(input: &'a str) -> IResult<&'a str, Command> {
|
fn execute(&self) -> Execution {
|
||||||
parser::parse_command(input)
|
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);
|
||||||
// Type subject to change
|
Execution {
|
||||||
pub fn execute(self) -> String {
|
plain,
|
||||||
match self {
|
html,
|
||||||
Command::Roll(command) => command.execute(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RollCommand {
|
pub fn parse_command(s: &str) -> Option<Result<Box<dyn Command>, String>> {
|
||||||
pub fn execute(self) -> String {
|
// Ignore trailing input, if any.
|
||||||
self.0.roll().to_string()
|
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::{
|
use nom::{
|
||||||
alt,
|
tuple,
|
||||||
|
switch,
|
||||||
|
take_while,
|
||||||
bytes::complete::{tag, take_while},
|
bytes::complete::{tag, take_while},
|
||||||
character::complete::digit1,
|
character::complete::digit1,
|
||||||
|
character::is_alphabetic,
|
||||||
complete, many0, named,
|
complete, many0, named,
|
||||||
sequence::tuple,
|
sequence::tuple,
|
||||||
tag, IResult,
|
tag, IResult,
|
||||||
|
@ -12,104 +15,30 @@ use crate::commands::{Command, RollCommand};
|
||||||
use crate::dice::parser::parse_element_expression;
|
use crate::dice::parser::parse_element_expression;
|
||||||
|
|
||||||
// Parse a roll expression.
|
// Parse a roll expression.
|
||||||
fn parse_roll(input: &str) -> IResult<&str, Command> {
|
fn parse_roll(input: &str) -> IResult<&str, RollCommand> {
|
||||||
named!(invocation(&str) -> &str, alt!(complete!(tag!("!r")) | complete!(tag!("!roll"))));
|
|
||||||
let (input, _) = eat_whitespace(input)?;
|
|
||||||
let (input, _) = invocation(input)?;
|
|
||||||
let (input, _) = eat_whitespace(input)?;
|
let (input, _) = eat_whitespace(input)?;
|
||||||
let (input, expression) = parse_element_expression(input)?;
|
let (input, expression) = parse_element_expression(input)?;
|
||||||
Ok((input, Command::Roll(RollCommand(expression))))
|
Ok((input, RollCommand(expression)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse a command expression.
|
// Potentially parse a command expression. If we recognize the command, an error should be raised
|
||||||
pub fn parse_command(input: &str) -> IResult<&str, Command> {
|
// if the command is misparsed. If we don't recognize the command, ignore it.
|
||||||
// Add new commands to alt!
|
pub fn parse_command(original_input: &str) -> IResult<&str, Option<Box<dyn Command>>> {
|
||||||
named!(command(&str) -> Command, alt!(parse_roll));
|
let (input, _) = eat_whitespace(original_input)?;
|
||||||
command(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;
|
pub mod parser;
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||||
|
@ -8,6 +9,12 @@ pub struct Dice {
|
||||||
pub(crate) sides: u32,
|
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 {
|
impl Dice {
|
||||||
fn new(count: u32, sides: u32) -> Dice {
|
fn new(count: u32, sides: u32) -> Dice {
|
||||||
Dice { count, sides }
|
Dice { count, sides }
|
||||||
|
@ -20,12 +27,30 @@ pub enum Element {
|
||||||
Bonus(u32),
|
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)]
|
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||||
pub enum SignedElement {
|
pub enum SignedElement {
|
||||||
Positive(Element),
|
Positive(Element),
|
||||||
Negative(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)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
pub struct ElementExpression(Vec<SignedElement>);
|
pub struct ElementExpression(Vec<SignedElement>);
|
||||||
|
|
||||||
|
@ -42,3 +67,20 @@ impl DerefMut for ElementExpression {
|
||||||
&mut self.0
|
&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 roll;
|
||||||
pub mod commands;
|
pub mod commands;
|
||||||
mod parser;
|
mod parser;
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,8 @@ fn is_whitespace(input: char) -> bool {
|
||||||
input == ' ' || input == '\n' || input == '\t' || input == '\r'
|
input == ' ' || input == '\n' || input == '\t' || input == '\r'
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn eat_whitespace(input: &str) -> IResult<&str, ()> {
|
/// Eat whitespace, returning it
|
||||||
let (input, _) = take_while(is_whitespace)(input)?;
|
pub fn eat_whitespace(input: &str) -> IResult<&str, &str> {
|
||||||
Ok((input, ()))
|
let (input, whitespace) = take_while(is_whitespace)(input)?;
|
||||||
|
Ok((input, whitespace))
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue