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.
This commit is contained in:
parent
1f5c6d7553
commit
da0819745a
|
@ -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::cofd::parser::{create_chance_die, parse_dice_pool};
|
||||||
use crate::commands::{Command, HelpCommand, PoolRollCommand, RollCommand};
|
use crate::commands::{Command, HelpCommand, PoolRollCommand, RollCommand};
|
||||||
use crate::dice::parser::parse_element_expression;
|
use crate::dice::parser::parse_element_expression;
|
||||||
use crate::help::parse_help_topic;
|
use crate::help::parse_help_topic;
|
||||||
use crate::parser::{eat_whitespace, trim};
|
use crate::parser::{eat_whitespace, trim};
|
||||||
|
use nom::{bytes::complete::tag, character::complete::alpha1, IResult};
|
||||||
|
|
||||||
// Parse a roll expression.
|
// Parse a roll expression.
|
||||||
fn parse_roll(input: &str) -> IResult<&str, Box<dyn Command>> {
|
fn parse_roll(input: &str) -> IResult<&str, Box<dyn Command>> {
|
||||||
|
@ -30,36 +29,88 @@ fn help(topic: &str) -> IResult<&str, Box<dyn Command>> {
|
||||||
Ok(("", Box::new(HelpCommand(topic))))
|
Ok(("", Box::new(HelpCommand(topic))))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Potentially parse a command expression. If we recognize the command, an error should be raised
|
/// Split an input string into its constituent command and "everything
|
||||||
/// if the command is misparsed. If we don't recognize the command, ignore it and return none
|
/// else" parts. Extracts the command separately from its input (i.e.
|
||||||
pub fn parse_command(original_input: &str) -> IResult<&str, Option<Box<dyn Command>>> {
|
/// rest of the line) and returns a tuple of (command_input, command).
|
||||||
let (input, _) = eat_whitespace(original_input)?;
|
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.
|
/// Potentially parse a command expression. If we recognize the
|
||||||
named!(command(&str) -> (&str, &str), tuple!(
|
/// command, an error should be raised if the command is misparsed. If
|
||||||
complete!(tag!("!")),
|
/// we don't recognize the command, ignore it and return none
|
||||||
alt!(
|
pub fn parse_command(input: &str) -> IResult<&str, Option<Box<dyn Command>>> {
|
||||||
//TODO figure out how to gracefully handle arbitrary single commands.
|
let (cmd_input, cmd) = split_command(input)?;
|
||||||
complete!(tag!("chance")) |
|
|
||||||
complete!(tag!("help")) |
|
|
||||||
complete!(take_while!(char::is_alphabetic))
|
|
||||||
)
|
|
||||||
));
|
|
||||||
|
|
||||||
let (input, command) = match command(input) {
|
match cmd {
|
||||||
// Strip the exclamation mark
|
"r" | "roll" => parse_roll(cmd_input).map(|(input, command)| (input, Some(command))),
|
||||||
Ok((input, (_, result))) => (input, result),
|
"rp" | "pool" => parse_pool_roll(cmd_input).map(|(input, command)| (input, Some(command))),
|
||||||
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))),
|
|
||||||
"chance" => chance_die().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.
|
// 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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue