diff --git a/src/parser.rs b/src/parser.rs index ff961bc..99e860b 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,4 +1,6 @@ +use combine::error::ParseError; use combine::parser::char::{digit, letter, spaces}; +use combine::stream::Stream; use combine::{many, many1, one_of, Parser}; use thiserror::Error; @@ -73,53 +75,71 @@ pub struct Amount { pub element: Element, } -/// Converts "+" or" -" into an Operator. No sign at all is an implied -/// Plus. Part of the parser. -fn map_operator(sign: char) -> Operator { - match sign { +/// Parser that attempt to convert the text at the start of the dice +/// parsing into an Amount instance. +fn first_amount_parser() -> impl Parser> +where + Input: Stream, + Input::Error: ParseError, +{ + let map_first_amount = |value: String| { + if value.chars().all(char::is_numeric) { + let num = value.parse::()?; + Ok(Amount { + operator: Operator::Plus, + element: Element::Number(num), + }) + } else { + Ok(Amount { + operator: Operator::Plus, + element: Element::Variable(value), + }) + } + }; + + many1(letter()) + .or(many1(digit())) + .skip(spaces().silent()) //Consume any space after first amount + .map(map_first_amount) +} + +/// Attempt to convert some text in the middle or end of the dice roll +/// string into an Amount. +fn amount_parser() -> impl Parser> +where + Input: Stream, + Input::Error: ParseError, +{ + let plus_or_minus = one_of("+-".chars()); + let parse_operator = plus_or_minus.map(|sign: char| match sign { '+' => Operator::Plus, '-' => Operator::Minus, _ => Operator::Plus, - } -} + }); -/// Attempt to convert the text at the start of the expression, -/// extracted by the Combine parser, into an Amount instance. Part of -/// the parser. -fn map_first_amount(value: String) -> ParseResult { - if value.chars().all(char::is_numeric) { - let num = value.parse::()?; - Ok(Amount { - operator: Operator::Plus, - element: Element::Number(num), - }) - } else { - Ok(Amount { - operator: Operator::Plus, - element: Element::Variable(value), - }) - } -} + // Element must either be a proper i32, or a variable name. + let map_element = |value: String| -> ParseResult { + if value.chars().all(char::is_numeric) { + let num = value.parse::()?; + Ok(Element::Number(num)) + } else { + Ok(Element::Variable(value)) + } + }; -/// Attempt to convert some text in the middle or end of the string, -/// extracted by the Combine parser, into an Element. Part of the -/// parser. -fn map_element(value: String) -> ParseResult { - if value.chars().all(char::is_numeric) { - let num = value.parse::()?; - Ok(Element::Number(num)) - } else { - Ok(Element::Variable(value)) - } -} + let parse_element = many1(letter()).or(many1(digit())).map(map_element); -/// Collect an Operator and Element into an Amount, but only if the -/// Element was successfully parsed. Part of the parser. -fn map_amount((operator, element_result): (Operator, ParseResult)) -> ParseResult { - match element_result { + let element_parser = parse_operator + .skip(spaces().silent()) + .and(parse_element) + .skip(spaces().silent()); + + let convert_to_amount = |(operator, element_result)| match element_result { Ok(element) => Ok(Amount { operator, element }), Err(e) => Err(e), - } + }; + + element_parser.map(convert_to_amount) } /// Parse an expression of numbers and/or variables into elements @@ -135,29 +155,8 @@ fn map_amount((operator, element_result): (Operator, ParseResult)) -> P pub fn parse_amounts(input: &str) -> ParseResult> { let input = input.trim(); - // Single sub-parser for the first amount expression, because it's - // easier. Wraps into ParseResult. - let first_amount = many1(letter()) - .or(many1(digit())) - .skip(spaces().silent()) //Consume any space after first amount - .map(map_first_amount); - - // All of this, down to amount_parser, will convert expressions - // after the first into Amounts (wrapped in a ParseResult). - let plus_or_minus = one_of("+-".chars()); - let maybe_sign = plus_or_minus.map(map_operator); - - let variable_or_number = many1(letter()).or(many1(digit())).map(map_element); - - let element_parser = maybe_sign - .skip(spaces().silent()) - .and(variable_or_number) - .skip(spaces().silent()); - - let amount_parser = element_parser.map(map_amount); - let remaining_amounts = many(amount_parser).map(|amounts: Vec>| amounts); - - let mut parser = first_amount.and(remaining_amounts); + let remaining_amounts = many(amount_parser()).map(|amounts: Vec>| amounts); + let mut parser = first_amount_parser().and(remaining_amounts); // Collapses first amount + remaining amounts into a single Vec, // while collecting extraneous input.