From da0819745a2fb5cee2297f307b4ce54dbd923528 Mon Sep 17 00:00:00 2001 From: projectmoon Date: Mon, 31 Aug 2020 00:07:56 +0000 Subject: [PATCH] Switch to non-macro nom parser with better text handling. By using the alpha1 function in complete mode, we are able to handle arbitrary single-word commands (e.g. "!help") and proprly map the remaining input to an empty string. --- src/commands/parser.rs | 109 ++++++++++++++++++++++++++++++----------- 1 file changed, 80 insertions(+), 29 deletions(-) diff --git a/src/commands/parser.rs b/src/commands/parser.rs index afd3e21..27fce63 100644 --- a/src/commands/parser.rs +++ b/src/commands/parser.rs @@ -1,10 +1,9 @@ -use nom::{alt, complete, named, tag, take_while, tuple, IResult}; - use crate::cofd::parser::{create_chance_die, parse_dice_pool}; use crate::commands::{Command, HelpCommand, PoolRollCommand, RollCommand}; use crate::dice::parser::parse_element_expression; use crate::help::parse_help_topic; use crate::parser::{eat_whitespace, trim}; +use nom::{bytes::complete::tag, character::complete::alpha1, IResult}; // Parse a roll expression. fn parse_roll(input: &str) -> IResult<&str, Box> { @@ -30,36 +29,88 @@ fn help(topic: &str) -> IResult<&str, Box> { Ok(("", Box::new(HelpCommand(topic)))) } -/// 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 and return none -pub fn parse_command(original_input: &str) -> IResult<&str, Option>> { - let (input, _) = eat_whitespace(original_input)?; +/// Split an input string into its constituent command and "everything +/// else" parts. Extracts the command separately from its input (i.e. +/// rest of the line) and returns a tuple of (command_input, command). +fn split_command(input: &str) -> IResult<&str, &str> { + let (input, _) = eat_whitespace(input)?; + let (input, _) = tag("!")(input)?; + let (command_input, command) = alpha1(input)?; + let (command_input, _) = eat_whitespace(command_input)?; + //TODO strip witespace from end of command input + Ok((command_input, command)) +} - //Parser understands either specific !commands with no input, or any !command with extra input. - named!(command(&str) -> (&str, &str), tuple!( - complete!(tag!("!")), - alt!( - //TODO figure out how to gracefully handle arbitrary single commands. - complete!(tag!("chance")) | - complete!(tag!("help")) | - complete!(take_while!(char::is_alphabetic)) - ) - )); +/// 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 and return none +pub fn parse_command(input: &str) -> IResult<&str, Option>> { + let (cmd_input, cmd) = split_command(input)?; - let (input, command) = match command(input) { - // Strip the exclamation mark - Ok((input, (_, result))) => (input, result), - Err(_e) => { - return Ok((original_input, None)); - } - }; - - match command { - "r" | "roll" => parse_roll(input).map(|(input, command)| (input, Some(command))), - "rp" | "pool" => parse_pool_roll(input).map(|(input, command)| (input, Some(command))), + match cmd { + "r" | "roll" => parse_roll(cmd_input).map(|(input, command)| (input, Some(command))), + "rp" | "pool" => parse_pool_roll(cmd_input).map(|(input, command)| (input, Some(command))), "chance" => chance_die().map(|(input, command)| (input, Some(command))), - "help" => help(input).map(|(input, command)| (input, Some(command))), + "help" => help(cmd_input).map(|(input, command)| (input, Some(command))), // No recognized command, ignore this. - _ => Ok((original_input, None)), + _ => Ok((input, None)), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn basic_command_test() { + assert_eq!( + ("1d4", "roll"), + split_command("!roll 1d4").expect("got parsing error") + ); + } + + #[test] + fn whitespace_at_start_test() { + assert_eq!( + ("1d4", "roll"), + split_command(" !roll 1d4").expect("got parsing error") + ); + } + + #[test] + fn whitespace_at_end_test() { + //TODO currently it does not chop whitespace off the end. + assert_eq!( + ("1d4 ", "roll"), + split_command("!roll 1d4 ").expect("got parsing error") + ); + } + + #[test] + fn whitespace_on_both_ends_test() { + //TODO currently it does not chop whitespace off the end. + assert_eq!( + ("1d4 ", "roll"), + split_command(" !roll 1d4 ").expect("got parsing error") + ); + } + + #[test] + fn single_command_test() { + assert_eq!( + ("", "roll"), + split_command("!roll").expect("got parsing error") + ); + + assert_eq!( + ("", "thisdoesnotexist"), + split_command("!thisdoesnotexist").expect("got parsing error") + ); + } + + #[test] + fn bad_command_test() { + assert!(split_command("roll 1d4").is_err()); + assert!(split_command("roll").is_err()); } }