Upgrade to nom v5

Error handling has changed, affecting the error variants LCI exports,
and macros are replaced by functions, so all the parsing code has been rewritten.
This commit is contained in:
Oliver Hamlet
2019-06-30 11:25:23 +01:00
parent cb19ec084a
commit 4db34be2c9
6 changed files with 280 additions and 264 deletions
+1 -1
View File
@@ -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"
+1 -2
View File
@@ -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,
}
+1 -1
View File
@@ -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() {
+83 -53
View File
@@ -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<error::Error>),
IoError(PathBuf, io::Error),
}
@@ -24,17 +23,11 @@ fn escape<I: fmt::Display>(input: I) -> String {
input.to_string().replace("\"", "\\\"")
}
impl<I: fmt::Debug + fmt::Display> From<Err<I, ParsingError>> for Error {
fn from(error: Err<I, ParsingError>) -> Self {
impl<I: fmt::Debug + fmt::Display> From<Err<ParsingError<I>>> for Error {
fn from(error: Err<ParsingError<I>>) -> 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<I: fmt::Debug + fmt::Display> {
input: I,
kind: ParsingErrorKind,
}
impl<I: fmt::Debug + fmt::Display> From<(I, ErrorKind)> for ParsingError<I> {
fn from((input, kind): (I, ErrorKind)) -> Self {
use nom::error::ParseError;
ParsingError::from_error_kind(input, kind)
}
}
impl<I: fmt::Debug + fmt::Display> fmt::Display for ParsingError<I> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"An error was encountered while parsing the expression \"{}\": {}",
self.input, self.kind
)
}
}
impl<I: fmt::Debug + fmt::Display> error::Error for ParsingError<I> {
fn cause(&self) -> Option<&error::Error> {
self.kind.cause()
}
}
impl<I: fmt::Debug + fmt::Display> nom::error::ParseError<I> for ParsingError<I> {
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<regex::Error> for ParsingError {
impl ParsingErrorKind {
pub fn at<I: fmt::Debug + fmt::Display>(self, input: I) -> ParsingError<I> {
ParsingError { input, kind: self }
}
}
impl From<regex::Error> 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<ParseIntError> for ParsingError {
impl From<ParseIntError> for ParsingErrorKind {
fn from(error: ParseIntError) -> Self {
ParsingError::InvalidCrc(error)
ParsingErrorKind::InvalidCrc(error)
}
}
impl From<u32> 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),
}
}
}
+145 -167
View File
@@ -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<CompleteStr, ComparisonOperator> {
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<Regex> {
RegexBuilder::new(input.as_ref())
fn parse_regex(input: &str) -> ParsingResult<Regex> {
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<CompleteStr, ParsingError> {
Err::Failure(Context::Code(
input,
ErrorKind::Custom(ParsingError::PathIsNotInGameDirectory(path)),
))
fn not_in_game_directory(input: &str, path: PathBuf) -> Err<ParsingError<&str>> {
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> {
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> {
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<PathBuf> {
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<PathBuf> {
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<PathBuf> {
/// 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<Regex> {
map_parser(is_not(INVALID_REGEX_PATH_CHARS), parse_regex)(input)
}
impl Function {
pub fn parse(input: CompleteStr) -> ParsingResult<Function> {
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<Function> {
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)
}
}
+49 -40
View File
@@ -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<CompleteStr<'a>, 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<Self, Self::Err> {
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<Expression> {
do_parse!(
input,
compound_conditions:
separated_list!(
fix_error!(ParsingError, ws!(tag!("or"))),
CompoundCondition::parse
)
>> (Expression(compound_conditions))
)
fn parse_expression(input: &str) -> ParsingResult<Expression> {
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<CompoundCondition> {
do_parse!(
input,
conditions:
separated_list!(fix_error!(ParsingError, ws!(tag!("and"))), Condition::parse)
>> (CompoundCondition(conditions))
)
fn parse(input: &str) -> ParsingResult<CompoundCondition> {
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<Condition> {
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<Condition> {
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()
);
}