diff --git a/Cargo.toml b/Cargo.toml index ef8c2c4..53354d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" [dependencies] crc = "1.0.0" -nom = "4.0.0" +nom = "5.0.0" pelite = "0.7.0" regex = "1.0.5" unicase = "2.2.0" diff --git a/ffi/src/helpers.rs b/ffi/src/helpers.rs index 369f8d1..3b04e3b 100644 --- a/ffi/src/helpers.rs +++ b/ffi/src/helpers.rs @@ -24,8 +24,7 @@ fn map_error(err: &Error) -> c_int { match err { Error::ParsingIncomplete => LCI_ERROR_PARSING_ERROR, Error::UnconsumedInput(_) => LCI_ERROR_PARSING_ERROR, - Error::GenericParsingError(_, _) => LCI_ERROR_PARSING_ERROR, - Error::CustomParsingError(_, _) => LCI_ERROR_PARSING_ERROR, + Error::ParsingError(_, _) => LCI_ERROR_PARSING_ERROR, Error::PeParsingError(_, _) => LCI_ERROR_PE_PARSING_ERROR, Error::IoError(_, _) => LCI_ERROR_IO_ERROR, } diff --git a/ffi/tests/ffi.cpp b/ffi/tests/ffi.cpp index 21f0f53..ab277cd 100644 --- a/ffi/tests/ffi.cpp +++ b/ffi/tests/ffi.cpp @@ -47,7 +47,7 @@ void test_lci_get_error_message() { return_code = lci_get_error_message(&message); assert(return_code == LCI_OK); assert(message != nullptr); - assert(strcmp(message, "An error was encountered in the parser \"SeparatedList\" while parsing the expression \"file(\\\"Blank.\"") == 0); + assert(strcmp(message, "An error was encountered while parsing the expression \"file(\\\"Blank.\": Error in parser: Separated list") == 0); } void test_lci_state_create() { diff --git a/src/error.rs b/src/error.rs index f68eb8d..88ba697 100644 --- a/src/error.rs +++ b/src/error.rs @@ -4,7 +4,8 @@ use std::io; use std::num::ParseIntError; use std::path::PathBuf; -use nom::{Context, Err, ErrorKind}; +use nom::error::ErrorKind; +use nom::Err; use regex; #[derive(Debug)] @@ -12,10 +13,8 @@ pub enum Error { ParsingIncomplete, // The string is the input that was not parsed. UnconsumedInput(String), - /// The first string is the expression parsed, the second is a tag describing the parser that failed. - GenericParsingError(String, String), - /// The string is the expression parsed. - CustomParsingError(String, ParsingError), + /// The string is the input at which the error was encountered. + ParsingError(String, ParsingErrorKind), PeParsingError(PathBuf, Box), IoError(PathBuf, io::Error), } @@ -24,17 +23,11 @@ fn escape(input: I) -> String { input.to_string().replace("\"", "\\\"") } -impl From> for Error { - fn from(error: Err) -> Self { +impl From>> for Error { + fn from(error: Err>) -> Self { match error { Err::Incomplete(_) => Error::ParsingIncomplete, - Err::Error(Context::Code(i, ErrorKind::Custom(e))) - | Err::Failure(Context::Code(i, ErrorKind::Custom(e))) => { - Error::CustomParsingError(escape(i), e) - } - Err::Error(Context::Code(i, e)) | Err::Failure(Context::Code(i, e)) => { - Error::GenericParsingError(escape(i), format!("{:?}", e)) - } + Err::Error(e) | Err::Failure(e) => Error::ParsingError(escape(e.input), e.kind), } } } @@ -48,12 +41,7 @@ impl fmt::Display for Error { "The parser did not consume the following input: \"{}\"", i ), - Error::GenericParsingError(i, e) => write!( - f, - "An error was encountered in the parser \"{}\" while parsing the expression \"{}\"", - e, i - ), - Error::CustomParsingError(i, e) => write!( + Error::ParsingError(i, e) => write!( f, "An error was encountered while parsing the expression \"{}\": {}", i, e @@ -77,7 +65,7 @@ impl fmt::Display for Error { impl error::Error for Error { fn cause(&self) -> Option<&error::Error> { match self { - Error::CustomParsingError(_, e) => Some(e), + Error::ParsingError(_, e) => Some(e), Error::PeParsingError(_, e) => Some(e.as_ref()), Error::IoError(_, e) => Some(e), _ => None, @@ -86,58 +74,100 @@ impl error::Error for Error { } #[derive(Debug)] -pub enum ParsingError { +pub struct ParsingError { + input: I, + kind: ParsingErrorKind, +} + +impl From<(I, ErrorKind)> for ParsingError { + fn from((input, kind): (I, ErrorKind)) -> Self { + use nom::error::ParseError; + ParsingError::from_error_kind(input, kind) + } +} + +impl fmt::Display for ParsingError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "An error was encountered while parsing the expression \"{}\": {}", + self.input, self.kind + ) + } +} + +impl error::Error for ParsingError { + fn cause(&self) -> Option<&error::Error> { + self.kind.cause() + } +} + +impl nom::error::ParseError for ParsingError { + fn from_error_kind(input: I, kind: ErrorKind) -> Self { + ParsingError { + input, + kind: ParsingErrorKind::GenericParserError(kind.description().to_string()), + } + } + + fn append(_: I, _: ErrorKind, other: Self) -> Self { + other + } +} + +#[derive(Debug)] +pub enum ParsingErrorKind { InvalidRegexSyntax(String), InvalidRegexUnknown, InvalidCrc(ParseIntError), PathEndsInADirectorySeparator(PathBuf), PathIsNotInGameDirectory(PathBuf), - Unknown(u32), + GenericParserError(String), } -impl From for ParsingError { +impl ParsingErrorKind { + pub fn at(self, input: I) -> ParsingError { + ParsingError { input, kind: self } + } +} + +impl From for ParsingErrorKind { fn from(error: regex::Error) -> Self { match error { - regex::Error::Syntax(s) => ParsingError::InvalidRegexSyntax(s), - _ => ParsingError::InvalidRegexUnknown, + regex::Error::Syntax(s) => ParsingErrorKind::InvalidRegexSyntax(s), + _ => ParsingErrorKind::InvalidRegexUnknown, } } } -impl From for ParsingError { +impl From for ParsingErrorKind { fn from(error: ParseIntError) -> Self { - ParsingError::InvalidCrc(error) + ParsingErrorKind::InvalidCrc(error) } } -impl From for ParsingError { - fn from(error: u32) -> Self { - ParsingError::Unknown(error) - } -} - -impl fmt::Display for ParsingError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - ParsingError::InvalidRegexSyntax(s) => write!(f, "{}", s), - ParsingError::InvalidRegexUnknown => write!(f, "Unknown regex parsing error"), - ParsingError::InvalidCrc(e) => e.fmt(f), - ParsingError::PathEndsInADirectorySeparator(p) => { - write!(f, "\"{}\" ends in a directory separator", p.display()) - } - ParsingError::PathIsNotInGameDirectory(p) => { - write!(f, "\"{}\" is not in the game directory", p.display()) - } - ParsingError::Unknown(e) => write!(f, "Unknown error code {}", e), - } - } -} - -impl error::Error for ParsingError { +impl error::Error for ParsingErrorKind { fn cause(&self) -> Option<&error::Error> { match self { - ParsingError::InvalidCrc(e) => Some(e), + ParsingErrorKind::InvalidCrc(e) => Some(e), _ => None, } } } + +impl fmt::Display for ParsingErrorKind { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + ParsingErrorKind::InvalidRegexSyntax(s) => write!(f, "{}", s), + ParsingErrorKind::InvalidRegexUnknown => write!(f, "Unknown regex parsing error"), + ParsingErrorKind::InvalidCrc(e) => e.fmt(f), + ParsingErrorKind::PathEndsInADirectorySeparator(p) => { + write!(f, "\"{}\" ends in a directory separator", p.display()) + } + ParsingErrorKind::PathIsNotInGameDirectory(p) => { + write!(f, "\"{}\" is not in the game directory", p.display()) + } + ParsingErrorKind::GenericParserError(e) => write!(f, "Error in parser: {}", e), + } + } +} diff --git a/src/function/parse.rs b/src/function/parse.rs index 58b095f..c0a22ba 100644 --- a/src/function/parse.rs +++ b/src/function/parse.rs @@ -1,29 +1,30 @@ use std::path::{Component, Path, PathBuf}; use std::str; -use nom::types::CompleteStr; -use nom::{hex_digit, Context, Err, ErrorKind, IResult}; +use nom::branch::alt; +use nom::bytes::complete::{is_not, tag}; +use nom::character::complete::hex_digit1; +use nom::combinator::{map, map_parser, value}; +use nom::sequence::{delimited, tuple}; +use nom::{Err, IResult}; use regex::{Regex, RegexBuilder}; use super::{ComparisonOperator, Function}; +use crate::{map_err, whitespace}; +use error::ParsingErrorKind; use ParsingError; use ParsingResult; impl ComparisonOperator { - pub fn parse(input: CompleteStr) -> IResult { - do_parse!( - input, - operator: - alt!( - tag!("==") => { |_| ComparisonOperator::Equal } | - tag!("!=") => { |_| ComparisonOperator::NotEqual } | - tag!("<=") => { |_| ComparisonOperator::LessThanOrEqual } | - tag!(">=") => { |_| ComparisonOperator::GreaterThanOrEqual } | - tag!("<") => { |_| ComparisonOperator::LessThan } | - tag!(">") => { |_| ComparisonOperator::GreaterThan } - ) - >> (operator) - ) + pub fn parse(input: &str) -> IResult<&str, ComparisonOperator> { + alt(( + value(ComparisonOperator::Equal, tag("==")), + value(ComparisonOperator::NotEqual, tag("!=")), + value(ComparisonOperator::LessThanOrEqual, tag("<=")), + value(ComparisonOperator::GreaterThanOrEqual, tag(">=")), + value(ComparisonOperator::LessThan, tag("<")), + value(ComparisonOperator::GreaterThan, tag(">")), + ))(input) } } @@ -45,47 +46,41 @@ fn is_in_game_path(path: &Path) -> bool { true } - -fn parse_regex(input: CompleteStr) -> ParsingResult { - RegexBuilder::new(input.as_ref()) +fn parse_regex(input: &str) -> ParsingResult { + RegexBuilder::new(input) .case_insensitive(true) .build() - .map(|r| (CompleteStr(""), r)) - .map_err(|e| { - Err::Failure(Context::Code( - input, - ErrorKind::Custom(ParsingError::from(e)), - )) - }) + .map(|r| ("", r)) + .map_err(|e| Err::Failure(ParsingErrorKind::from(e).at(input))) } -fn not_in_game_directory(input: CompleteStr, path: PathBuf) -> Err { - Err::Failure(Context::Code( - input, - ErrorKind::Custom(ParsingError::PathIsNotInGameDirectory(path)), - )) +fn not_in_game_directory(input: &str, path: PathBuf) -> Err> { + Err::Failure(ParsingErrorKind::PathIsNotInGameDirectory(path).at(input)) } -fn parse_version_args(input: CompleteStr) -> ParsingResult<(PathBuf, String, ComparisonOperator)> { - let (remaining_input, (path, version, comparator)) = try_parse!( - input, - fix_error!( - ParsingError, - do_parse!( - tag!("\"") - >> path: is_not!(INVALID_PATH_CHARS) - >> tag!("\"") - >> ws!(tag!(",")) - >> tag!("\"") - >> version: is_not!("\"") - >> tag!("\"") - >> ws!(tag!(",")) - >> operator: call!(ComparisonOperator::parse) - >> (PathBuf::from(path.as_ref()), version.to_string(), operator) - ) - ) +fn parse_path(input: &str) -> IResult<&str, PathBuf> { + map( + delimited(tag("\""), is_not(INVALID_PATH_CHARS), tag("\"")), + PathBuf::from, + )(input) +} + +fn parse_version_args(input: &str) -> ParsingResult<(PathBuf, String, ComparisonOperator)> { + let version_parser = map( + delimited(tag("\""), is_not("\""), tag("\"")), + |version: &str| version.to_string(), ); + let parser = tuple(( + parse_path, + whitespace(tag(",")), + version_parser, + whitespace(tag(",")), + ComparisonOperator::parse, + )); + + let (remaining_input, (path, _, version, _, comparator)) = map_err(parser)(input)?; + if is_in_game_path(&path) { Ok((remaining_input, (path, version, comparator))) } else { @@ -93,29 +88,20 @@ fn parse_version_args(input: CompleteStr) -> ParsingResult<(PathBuf, String, Com } } -fn parse_crc(input: CompleteStr) -> ParsingResult { - u32::from_str_radix(input.as_ref(), 16) - .map(|c| (CompleteStr(""), c)) - .map_err(|e| { - Err::Failure(Context::Code( - input, - ErrorKind::Custom(ParsingError::from(e)), - )) - }) +fn parse_crc(input: &str) -> ParsingResult { + u32::from_str_radix(input, 16) + .map(|c| ("", c)) + .map_err(|e| Err::Failure(ParsingErrorKind::from(e).at(input))) } -fn parse_checksum_args(input: CompleteStr) -> ParsingResult<(PathBuf, u32)> { - let (remaining_input, (path, crc)) = try_parse!( - input, - do_parse!( - fix_error!(ParsingError, tag!("\"")) - >> path: fix_error!(ParsingError, is_not!(INVALID_PATH_CHARS)) - >> fix_error!(ParsingError, tag!("\"")) - >> fix_error!(ParsingError, ws!(tag!(","))) - >> crc: flat_map!(fix_error!(ParsingError, hex_digit), parse_crc) - >> (PathBuf::from(path.as_ref()), crc) - ) - ); +fn parse_checksum_args(input: &str) -> ParsingResult<(PathBuf, u32)> { + let parser = tuple(( + map_err(parse_path), + map_err(whitespace(tag(","))), + map_parser(hex_digit1, parse_crc), + )); + + let (remaining_input, (path, _, crc)) = parser(input)?; if is_in_game_path(&path) { Ok((remaining_input, (path, crc))) @@ -124,16 +110,10 @@ fn parse_checksum_args(input: CompleteStr) -> ParsingResult<(PathBuf, u32)> { } } -fn parse_non_regex_path(input: CompleteStr) -> ParsingResult { - let (remaining_input, path) = try_parse!( - input, - fix_error!( - ParsingError, - map!(is_not!(INVALID_NON_REGEX_PATH_CHARS), |s| PathBuf::from( - s.as_ref() - )) - ) - ); +fn parse_non_regex_path(input: &str) -> ParsingResult { + let (remaining_input, path) = map(is_not(INVALID_NON_REGEX_PATH_CHARS), |path: &str| { + PathBuf::from(path) + })(input)?; if is_in_game_path(&path) { Ok((remaining_input, path)) @@ -144,19 +124,13 @@ fn parse_non_regex_path(input: CompleteStr) -> ParsingResult { /// Parse a string that is a path where the last component is a regex string /// that may contain characters that are invalid in paths but valid in regex. -fn parse_regex_path(input: CompleteStr) -> ParsingResult<(PathBuf, Regex)> { - let (remaining_input, string) = try_parse!( - input, - fix_error!(ParsingError, is_not!(INVALID_REGEX_PATH_CHARS)) - ); +fn parse_regex_path(input: &str) -> ParsingResult<(PathBuf, Regex)> { + let (remaining_input, string) = is_not(INVALID_REGEX_PATH_CHARS)(input)?; if string.ends_with('/') { - return Err(Err::Failure(Context::Code( - input, - ErrorKind::Custom(ParsingError::PathEndsInADirectorySeparator( - string.as_ref().into(), - )), - ))); + return Err(Err::Failure( + ParsingErrorKind::PathEndsInADirectorySeparator(string.into()).at(input), + )); } let (parent_path_slice, regex_slice) = string @@ -170,87 +144,91 @@ fn parse_regex_path(input: CompleteStr) -> ParsingResult<(PathBuf, Regex)> { return Err(not_in_game_directory(input, parent_path)); } - let regex = parse_regex(CompleteStr(regex_slice))?.1; + let regex = parse_regex(regex_slice)?.1; Ok((remaining_input, (parent_path, regex))) } +fn parse_regex_filename(input: &str) -> ParsingResult { + map_parser(is_not(INVALID_REGEX_PATH_CHARS), parse_regex)(input) +} + impl Function { - pub fn parse(input: CompleteStr) -> ParsingResult { - do_parse!( - input, - function: - alt!( - delimited!( - fix_error!(ParsingError, tag!("file(\"")), - call!(parse_non_regex_path), - fix_error!(ParsingError, tag!("\")")) - ) => { - |path| Function::FilePath(path) - } | - delimited!( - fix_error!(ParsingError, tag!("file(\"")), - call!(parse_regex_path), - fix_error!(ParsingError, tag!("\")")) - ) => { - |(p, r)| Function::FileRegex(p, r) - } | - delimited!( - fix_error!(ParsingError, tag!("active(\"")), - call!(parse_non_regex_path), - fix_error!(ParsingError, tag!("\")")) - ) => { - |path| Function::ActivePath(path) - } | - delimited!( - fix_error!(ParsingError, tag!("active(\"")), - flat_map!(fix_error!(ParsingError, is_not!(INVALID_REGEX_PATH_CHARS)), parse_regex), - fix_error!(ParsingError, tag!("\")")) - ) => { - |r| Function::ActiveRegex(r) - } | - delimited!( - fix_error!(ParsingError, tag!("many(\"")), - call!(parse_regex_path), - fix_error!(ParsingError, tag!("\")")) - ) => { - |(p, r)| Function::Many(p, r) - } | - delimited!( - fix_error!(ParsingError, tag!("many_active(\"")), - flat_map!(fix_error!(ParsingError, is_not!(INVALID_REGEX_PATH_CHARS)), parse_regex), - fix_error!(ParsingError, tag!("\")")) - ) => { - |r| Function::ManyActive(r) - } | - delimited!( - fix_error!(ParsingError, tag!("version(")), - call!(parse_version_args), - fix_error!(ParsingError, tag!(")")) - ) => { - |(path, version, comparator)| { - Function::Version(path, version, comparator) - } - } | - delimited!( - fix_error!(ParsingError, tag!("product_version(")), - call!(parse_version_args), - fix_error!(ParsingError, tag!(")")) - ) => { - |(path, version, comparator)| { - Function::ProductVersion(path, version, comparator) - } - } | - delimited!( - fix_error!(ParsingError, tag!("checksum(")), - call!(parse_checksum_args), - fix_error!(ParsingError, tag!(")")) - ) => { - |(path, crc)| Function::Checksum(path, crc) - } - ) - >> (function) - ) + pub fn parse(input: &str) -> ParsingResult { + alt(( + map( + delimited( + map_err(tag("file(\"")), + parse_non_regex_path, + map_err(tag("\")")), + ), + Function::FilePath, + ), + map( + delimited( + map_err(tag("file(\"")), + parse_regex_path, + map_err(tag("\")")), + ), + |(path, regex)| Function::FileRegex(path, regex), + ), + map( + delimited( + map_err(tag("active(\"")), + parse_non_regex_path, + map_err(tag("\")")), + ), + Function::ActivePath, + ), + map( + delimited( + map_err(tag("active(\"")), + parse_regex_filename, + map_err(tag("\")")), + ), + Function::ActiveRegex, + ), + map( + delimited( + map_err(tag("many(\"")), + parse_regex_path, + map_err(tag("\")")), + ), + |(path, regex)| Function::Many(path, regex), + ), + map( + delimited( + map_err(tag("many_active(\"")), + parse_regex_filename, + map_err(tag("\")")), + ), + Function::ManyActive, + ), + map( + delimited( + map_err(tag("version(")), + parse_version_args, + map_err(tag(")")), + ), + |(path, version, comparator)| Function::Version(path, version, comparator), + ), + map( + delimited( + map_err(tag("product_version(")), + parse_version_args, + map_err(tag(")")), + ), + |(path, version, comparator)| Function::ProductVersion(path, version, comparator), + ), + map( + delimited( + map_err(tag("checksum(")), + parse_checksum_args, + map_err(tag(")")), + ), + |(path, crc)| Function::Checksum(path, crc), + ), + ))(input) } } diff --git a/src/lib.rs b/src/lib.rs index e7d6da6..3a23479 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,4 @@ extern crate crc; -#[macro_use] extern crate nom; extern crate pelite; extern crate regex; @@ -20,13 +19,19 @@ use std::path::{Path, PathBuf}; use std::str; use std::sync::{PoisonError, RwLock, RwLockWriteGuard}; -use nom::types::CompleteStr; +use nom::branch::alt; +use nom::bytes::complete::tag; +use nom::character::complete::space0; +use nom::combinator::map; +use nom::multi::separated_list; +use nom::sequence::{delimited, preceded}; use nom::IResult; -pub use error::{Error, ParsingError}; +use error::ParsingError; +pub use error::{Error, ParsingErrorKind}; use function::Function; -type ParsingResult<'a, T> = IResult, T, ParsingError>; +type ParsingResult<'a, T> = IResult<&'a str, T, ParsingError<&'a str>>; // GameType variants must not change order, as their integer values are used as // constants in the C API. @@ -163,7 +168,7 @@ impl str::FromStr for Expression { type Err = Error; fn from_str(s: &str) -> Result { - parse_expression(nom::types::CompleteStr(s)) + parse_expression(s) .map_err(Error::from) .and_then(|(remaining_input, expression)| { if remaining_input.is_empty() { @@ -175,16 +180,11 @@ impl str::FromStr for Expression { } } -fn parse_expression(input: nom::types::CompleteStr) -> ParsingResult { - do_parse!( - input, - compound_conditions: - separated_list!( - fix_error!(ParsingError, ws!(tag!("or"))), - CompoundCondition::parse - ) - >> (Expression(compound_conditions)) - ) +fn parse_expression(input: &str) -> ParsingResult { + map( + separated_list(map_err(whitespace(tag("or"))), CompoundCondition::parse), + Expression, + )(input) } impl fmt::Display for Expression { @@ -208,13 +208,11 @@ impl CompoundCondition { Ok(true) } - fn parse(input: nom::types::CompleteStr) -> ParsingResult { - do_parse!( - input, - conditions: - separated_list!(fix_error!(ParsingError, ws!(tag!("and"))), Condition::parse) - >> (CompoundCondition(conditions)) - ) + fn parse(input: &str) -> ParsingResult { + map( + separated_list(map_err(whitespace(tag("and"))), Condition::parse), + CompoundCondition, + )(input) } } @@ -241,23 +239,22 @@ impl Condition { } } - fn parse(input: nom::types::CompleteStr) -> ParsingResult { - do_parse!( - input, - condition: - alt!( - call!(Function::parse) => { - |f| Condition::Function(f) - } | - preceded!(fix_error!(ParsingError, ws!(tag!("not"))), call!(Function::parse)) => { - |f| Condition::InvertedFunction(f) - } | - delimited!(fix_error!(ParsingError, ws!(tag!("("))), call!(parse_expression), fix_error!(ParsingError, ws!(tag!(")")))) => { - |e| Condition::Expression(e) - } - ) - >> (condition) - ) + fn parse(input: &str) -> ParsingResult { + alt(( + map(Function::parse, Condition::Function), + map( + preceded(map_err(whitespace(tag("not"))), Function::parse), + Condition::InvertedFunction, + ), + map( + delimited( + map_err(whitespace(tag("("))), + parse_expression, + map_err(whitespace(tag(")"))), + ), + Condition::Expression, + ), + ))(input) } } @@ -272,6 +269,18 @@ impl fmt::Display for Condition { } } +fn map_err<'a, O>( + parser: impl Fn(&'a str) -> IResult<&'a str, O, (&'a str, nom::error::ErrorKind)>, +) -> impl Fn(&'a str) -> ParsingResult<'a, O> { + move |i| parser(i).map_err(nom::Err::convert) +} + +fn whitespace<'a, O>( + parser: impl Fn(&'a str) -> IResult<&'a str, O>, +) -> impl Fn(&'a str) -> IResult<&'a str, O> { + delimited(space0, parser, space0) +} + #[cfg(test)] mod tests { use super::*; @@ -452,7 +461,7 @@ mod tests { let error = Expression::from_str("file(\"Carg").unwrap_err(); assert_eq!( - "An error was encountered in the parser \"SeparatedList\" while parsing the expression \"file(\\\"Carg\"", + "An error was encountered while parsing the expression \"file(\\\"Carg\": Error in parser: Separated list", error.to_string() ); }