From 56de3d34f1c2669664943e0ad770110cc3ecc658 Mon Sep 17 00:00:00 2001 From: "Taylor C. Richberger" Date: Mon, 20 Apr 2020 00:32:57 -0600 Subject: [PATCH] write out nom-based parser --- Cargo.lock | 112 +++++++++++++++++---------- Cargo.toml | 12 +-- src/bin/dicebot-roll.rs | 125 +----------------------------- src/lib.rs | 1 + src/parser.rs | 164 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 247 insertions(+), 167 deletions(-) create mode 100644 src/parser.rs diff --git a/Cargo.lock b/Cargo.lock index bacfa52..00bfec5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,20 +1,20 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -[[package]] -name = "aho-corasick" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada" -dependencies = [ - "memchr", -] - [[package]] name = "arc-swap" version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d663a8e9a99154b5fb793032533f6328da35e23aac63d5c152279aa8ba356825" +[[package]] +name = "arrayvec" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9" +dependencies = [ + "nodrop", +] + [[package]] name = "autocfg" version = "1.0.0" @@ -25,7 +25,7 @@ checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" name = "axfive-matrix-dicebot" version = "0.1.0" dependencies = [ - "regex", + "nom", "reqwest", "serde", "serde_json", @@ -65,9 +65,9 @@ checksum = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd" [[package]] name = "cfg-if" -version = "0.1.10" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" +checksum = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33" [[package]] name = "core-foundation" @@ -329,6 +329,20 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lexical-core" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f86d66d380c9c5a685aaac7a11818bdfa1f733198dfd9ec09c70b762cd12ad6f" +dependencies = [ + "arrayvec", + "bitflags", + "cfg-if", + "rustc_version", + "ryu", + "static_assertions", +] + [[package]] name = "libc" version = "0.2.69" @@ -443,6 +457,23 @@ dependencies = [ "winapi 0.3.8", ] +[[package]] +name = "nodrop" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" + +[[package]] +name = "nom" +version = "5.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b471253da97532da4b61552249c521e01e736071f71c1a4f7ebbfbf0a06aad6" +dependencies = [ + "lexical-core", + "memchr", + "version_check", +] + [[package]] name = "openssl" version = "0.10.29" @@ -591,24 +622,6 @@ version = "0.1.56" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" -[[package]] -name = "regex" -version = "1.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6020f034922e3194c711b82a627453881bc4682166cabb07134a10c26ba7692" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", - "thread_local", -] - -[[package]] -name = "regex-syntax" -version = "0.6.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae" - [[package]] name = "remove_dir_all" version = "0.5.2" @@ -653,6 +666,15 @@ dependencies = [ "winreg", ] +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + [[package]] name = "ryu" version = "1.0.3" @@ -692,6 +714,21 @@ dependencies = [ "libc", ] +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + [[package]] name = "serde" version = "1.0.106" @@ -757,6 +794,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05720e22615919e4734f6a99ceae50d00226c3c5aca406e102ebc33298214e0a" +[[package]] +name = "static_assertions" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f3eb36b47e512f8f1c9e3d10c2c1965bc992bd9cdb024fa581e2194501c83d3" + [[package]] name = "syn" version = "1.0.17" @@ -782,15 +825,6 @@ dependencies = [ "winapi 0.3.8", ] -[[package]] -name = "thread_local" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" -dependencies = [ - "lazy_static", -] - [[package]] name = "time" version = "0.1.42" diff --git a/Cargo.toml b/Cargo.toml index 440832a..d708ff6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,15 +7,15 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -reqwest = "^0.10" -serde_json = "^1" -toml = "^0.5" -regex = "^1.3" +reqwest = "0.10" +serde_json = "1" +toml = "0.5" +nom = "5" [dependencies.serde] -version = "^1" +version = "1" features = ['derive'] [dependencies.tokio] -version = "^0.2" +version = "0.2" features = ["rt-core", "macros", "time", "signal"] diff --git a/src/bin/dicebot-roll.rs b/src/bin/dicebot-roll.rs index ac15fb9..6eefa52 100644 --- a/src/bin/dicebot-roll.rs +++ b/src/bin/dicebot-roll.rs @@ -1,128 +1,9 @@ -use regex::Regex; use std::error::Error; -use std::fmt; -use std::str::FromStr; - -#[derive(Debug)] -struct ParseError { - reason: String, -} - -impl fmt::Display for ParseError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "ParseError: {}", self.reason) - } -} - -impl Error for ParseError {} - -#[derive(Debug)] -struct Dice { - count: u32, - sides: u32, -} - -#[derive(Debug)] -enum Roll { - Dice(Dice), - Bonus(u32), -} - -impl From for Roll { - fn from(value: u32) -> Roll { - Roll::Bonus(value) - } -} - -impl From for Roll { - fn from(value: Dice) -> Roll { - Roll::Dice(value) - } -} - -impl FromStr for Roll { - type Err = Box; - - fn from_str(s: &str) -> Result { - let s = s.trim(); - let regex = Regex::new(r"(?i)^(\d+)\s*(d\s*(\d+))?$")?; - let captures = match regex.captures(s) { - Some(captures) => captures, - None => { - return Err(ParseError { - reason: format!("{:?} is not a legal Roll Part expression", s), - } - .into()) - } - }; - - match captures.get(2) { - Some(_) => Ok(Dice { - count: captures.get(1).unwrap().as_str().parse()?, - sides: captures.get(3).unwrap().as_str().parse()?, - } - .into()), - None => Ok(Roll::Bonus(captures.get(1).unwrap().as_str().parse()?)), - } - } -} - -#[derive(Debug)] -enum Part { - Plus(Roll), - Minus(Roll), -} - -impl FromStr for Part { - type Err = Box; - - fn from_str(s: &str) -> Result { - let s = s.trim(); - - match s.chars().next() { - Some('+') => Ok(Part::Plus(s[1..].parse()?)), - Some('-') => Ok(Part::Minus(s[1..].parse()?)), - Some(_) => Ok(Part::Plus(s.parse()?)), - None => Err(ParseError { - reason: format!("{:?} is not a legal Roll Part expression", s), - } - .into()), - } - } -} - -#[derive(Debug)] -struct Expression(Vec); - -impl FromStr for Expression { - type Err = Box; - - fn from_str(s: &str) -> Result { - let s = s.trim(); - let validation_regex = Regex::new( - r"(?xi)^ - ([-\+])?\s*(\d+)\s*(d\s*(\d+))? - (\s*([-\+])\s*(\d+)\s*(d\s*(\d+))?)* - $", - )?; - if !validation_regex.is_match(s) { - return Err(ParseError { - reason: format!("{:?} is not a legal dice expression", s), - } - .into()); - } - - let part = Regex::new(r"(?i)[-\+]?\s*\d+\s*(d\s*(\d+))?")?; - - let results: Result<_, _> = part.find_iter(s).map(|p| p.as_str().parse()).collect(); - Ok(Expression(results?)) - } -} fn main() -> Result<(), Box> { - let roll_string = std::env::args().skip(1).collect::>().join(" "); + let _roll_string = std::env::args().skip(1).collect::>().join(" "); // first regex needs to be different because the sign is mandatory for the rest - let expression: Expression = roll_string.parse()?; - println!("{:?}", expression); + //let expression = parse_expression(&roll_string); + //println!("{:?}", expression); Ok(()) } diff --git a/src/lib.rs b/src/lib.rs index 54a5004..bc0754b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,2 +1,3 @@ pub mod bot; pub mod matrix; +pub mod parser; diff --git a/src/parser.rs b/src/parser.rs new file mode 100644 index 0000000..b510247 --- /dev/null +++ b/src/parser.rs @@ -0,0 +1,164 @@ +use nom::{ + alt, + tag, + complete, + named, + many0, + IResult, + bytes::complete::{tag, take_while}, + character::complete::digit1, + sequence::tuple +}; + +#[derive(Debug, PartialEq, Eq)] +struct Dice { + count: u32, + sides: u32, +} + +impl Dice { + fn new(count: u32, sides: u32) -> Dice { + Dice { + count, + sides, + } + } +} + +#[derive(Debug, PartialEq, Eq)] +enum Element { + Dice(Dice), + Bonus(u32), +} + +#[derive(Debug, PartialEq, Eq)] +enum Sign { + Plus, + Minus, +} + +#[derive(Debug, PartialEq, Eq)] +enum SignedElement { + Positive(Element), + Negative(Element), +} + +#[derive(Debug, PartialEq, Eq)] +struct ElementExpression(Vec); + +fn is_whitespace(input: char) -> bool { + input == ' ' || input == '\n' || input == '\t' || input == '\r' +} + +fn eat_whitespace(input: &str) -> IResult<&str, ()> { + let (input, _) = take_while(is_whitespace)(input)?; + Ok((input, ())) +} + +// Parse a dice expression. Does not eat whitespace +fn parse_dice(input: &str) -> IResult<&str, Dice> { + let (input, (count, _, sides)) = tuple((digit1, tag("d"), digit1))(input)?; + Ok((input, Dice::new(count.parse().unwrap(), sides.parse().unwrap()))) +} + +// Parse a single digit expression. Does not eat whitespace +fn parse_bonus(input: &str) -> IResult<&str, u32> { + let (input, bonus) = digit1(input)?; + Ok((input, bonus.parse().unwrap())) +} + +// Parse a sign expression. Eats whitespace. +fn parse_sign(input: &str) -> IResult<&str, Sign> { + let (input, _) = eat_whitespace(input)?; + named!(sign(&str) -> Sign, alt!( + tag!("+") => { |_| Sign::Plus } | + tag!("-") => { |_| Sign::Minus } + )); + + let (input, sign) = sign(input)?; + Ok((input, sign)) +} + +// Parse an element expression. Eats whitespace. +fn parse_element(input: &str) -> IResult<&str, Element> { + let (input, _) = eat_whitespace(input)?; + named!(element(&str) -> Element, alt!( + parse_dice => { |d| Element::Dice(d) } | + parse_bonus => { |b| Element::Bonus(b) } + )); + + let (input, element) = element(input)?; + Ok((input, element)) +} + +// Parse a signed element expression. Eats whitespace. +fn parse_signed_element(input: &str) -> IResult<&str, SignedElement> { + let (input, _) = eat_whitespace(input)?; + let (input, sign) = parse_sign(input)?; + let (input, _) = eat_whitespace(input)?; + + let (input, element) = parse_element(input)?; + let element = match sign { + Sign::Plus => SignedElement::Positive(element), + Sign::Minus => SignedElement::Negative(element), + }; + Ok((input, element)) +} + +// Parse a full element expression. Eats whitespace. +fn parse_element_expression(input: &str) -> IResult<&str, ElementExpression> { + named!(first_element(&str) -> SignedElement, alt!( + parse_signed_element => { |e| e } | + parse_element => { |e| SignedElement::Positive(e) } + )); + let (input, first) = first_element(input)?; + let (input, rest) = if input.trim().is_empty() { + (input, vec![first]) + } else { + named!(rest_elements(&str) -> Vec, many0!(complete!(parse_signed_element))); + let (input, mut rest) = rest_elements(input)?; + rest.insert(0, first); + (input, rest) + }; + + Ok((input, ElementExpression(rest))) +} + +#[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)), + ])))); + } +}