write out nom-based parser

This commit is contained in:
Taylor C. Richberger 2020-04-20 00:32:57 -06:00
parent 18537f9ab0
commit 56de3d34f1
5 changed files with 247 additions and 167 deletions

112
Cargo.lock generated
View File

@ -1,20 +1,20 @@
# This file is automatically @generated by Cargo. # This file is automatically @generated by Cargo.
# It is not intended for manual editing. # 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]] [[package]]
name = "arc-swap" name = "arc-swap"
version = "0.4.5" version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d663a8e9a99154b5fb793032533f6328da35e23aac63d5c152279aa8ba356825" checksum = "d663a8e9a99154b5fb793032533f6328da35e23aac63d5c152279aa8ba356825"
[[package]]
name = "arrayvec"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9"
dependencies = [
"nodrop",
]
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.0.0" version = "1.0.0"
@ -25,7 +25,7 @@ checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
name = "axfive-matrix-dicebot" name = "axfive-matrix-dicebot"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"regex", "nom",
"reqwest", "reqwest",
"serde", "serde",
"serde_json", "serde_json",
@ -65,9 +65,9 @@ checksum = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd"
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "0.1.10" version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" checksum = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33"
[[package]] [[package]]
name = "core-foundation" name = "core-foundation"
@ -329,6 +329,20 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 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]] [[package]]
name = "libc" name = "libc"
version = "0.2.69" version = "0.2.69"
@ -443,6 +457,23 @@ dependencies = [
"winapi 0.3.8", "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]] [[package]]
name = "openssl" name = "openssl"
version = "0.10.29" version = "0.10.29"
@ -591,24 +622,6 @@ version = "0.1.56"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" 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]] [[package]]
name = "remove_dir_all" name = "remove_dir_all"
version = "0.5.2" version = "0.5.2"
@ -653,6 +666,15 @@ dependencies = [
"winreg", "winreg",
] ]
[[package]]
name = "rustc_version"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
dependencies = [
"semver",
]
[[package]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.3" version = "1.0.3"
@ -692,6 +714,21 @@ dependencies = [
"libc", "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]] [[package]]
name = "serde" name = "serde"
version = "1.0.106" version = "1.0.106"
@ -757,6 +794,12 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05720e22615919e4734f6a99ceae50d00226c3c5aca406e102ebc33298214e0a" checksum = "05720e22615919e4734f6a99ceae50d00226c3c5aca406e102ebc33298214e0a"
[[package]]
name = "static_assertions"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f3eb36b47e512f8f1c9e3d10c2c1965bc992bd9cdb024fa581e2194501c83d3"
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.17" version = "1.0.17"
@ -782,15 +825,6 @@ dependencies = [
"winapi 0.3.8", "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]] [[package]]
name = "time" name = "time"
version = "0.1.42" version = "0.1.42"

View File

@ -7,15 +7,15 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
reqwest = "^0.10" reqwest = "0.10"
serde_json = "^1" serde_json = "1"
toml = "^0.5" toml = "0.5"
regex = "^1.3" nom = "5"
[dependencies.serde] [dependencies.serde]
version = "^1" version = "1"
features = ['derive'] features = ['derive']
[dependencies.tokio] [dependencies.tokio]
version = "^0.2" version = "0.2"
features = ["rt-core", "macros", "time", "signal"] features = ["rt-core", "macros", "time", "signal"]

View File

@ -1,128 +1,9 @@
use regex::Regex;
use std::error::Error; 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<u32> for Roll {
fn from(value: u32) -> Roll {
Roll::Bonus(value)
}
}
impl From<Dice> for Roll {
fn from(value: Dice) -> Roll {
Roll::Dice(value)
}
}
impl FromStr for Roll {
type Err = Box<dyn Error>;
fn from_str(s: &str) -> Result<Self, Self::Err> {
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<dyn Error>;
fn from_str(s: &str) -> Result<Self, Self::Err> {
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<Part>);
impl FromStr for Expression {
type Err = Box<dyn Error>;
fn from_str(s: &str) -> Result<Self, Self::Err> {
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<dyn Error>> { fn main() -> Result<(), Box<dyn Error>> {
let roll_string = std::env::args().skip(1).collect::<Vec<String>>().join(" "); let _roll_string = std::env::args().skip(1).collect::<Vec<String>>().join(" ");
// first regex needs to be different because the sign is mandatory for the rest // first regex needs to be different because the sign is mandatory for the rest
let expression: Expression = roll_string.parse()?; //let expression = parse_expression(&roll_string);
println!("{:?}", expression); //println!("{:?}", expression);
Ok(()) Ok(())
} }

View File

@ -1,2 +1,3 @@
pub mod bot; pub mod bot;
pub mod matrix; pub mod matrix;
pub mod parser;

164
src/parser.rs Normal file
View File

@ -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<SignedElement>);
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<SignedElement>, 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)),
]))));
}
}