use guppy::graph::cargo::BuildPlatform;
use miette::SourceSpan;
use std::fmt;
use winnow::{
ascii::line_ending,
combinator::{alt, delimited, eof, peek, preceded, repeat, terminated, trace},
stream::{Location, SliceLen, Stream},
token::{literal, take_till},
Parser,
};
mod glob;
mod unicode_string;
use crate::{errors::*, NameMatcher};
pub(crate) use glob::GenericGlob;
pub(crate) use unicode_string::DisplayParsedString;
pub(crate) type Span<'a> = winnow::Stateful<winnow::Located<&'a str>, State<'a>>;
type Error = ();
type PResult<T> = winnow::PResult<T, Error>;
pub(crate) fn new_span<'a>(input: &'a str, errors: &'a mut Vec<ParseSingleError>) -> Span<'a> {
Span {
input: winnow::Located::new(input),
state: State::new(errors),
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[doc(hidden)]
pub enum SetDef<S = SourceSpan> {
Package(NameMatcher, S),
Deps(NameMatcher, S),
Rdeps(NameMatcher, S),
Kind(NameMatcher, S),
Binary(NameMatcher, S),
BinaryId(NameMatcher, S),
Platform(BuildPlatform, S),
Test(NameMatcher, S),
Default(S),
All,
None,
}
impl SetDef {
#[cfg(test)]
fn drop_source_span(self) -> SetDef<()> {
match self {
Self::Package(matcher, _) => SetDef::Package(matcher, ()),
Self::Deps(matcher, _) => SetDef::Deps(matcher, ()),
Self::Rdeps(matcher, _) => SetDef::Rdeps(matcher, ()),
Self::Kind(matcher, _) => SetDef::Kind(matcher, ()),
Self::Binary(matcher, _) => SetDef::Binary(matcher, ()),
Self::BinaryId(matcher, _) => SetDef::BinaryId(matcher, ()),
Self::Platform(platform, _) => SetDef::Platform(platform, ()),
Self::Test(matcher, _) => SetDef::Test(matcher, ()),
Self::Default(_) => SetDef::Default(()),
Self::All => SetDef::All,
Self::None => SetDef::None,
}
}
}
impl<S> fmt::Display for SetDef<S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Package(matcher, _) => write!(f, "package({matcher})"),
Self::Deps(matcher, _) => write!(f, "deps({matcher})"),
Self::Rdeps(matcher, _) => write!(f, "rdeps({matcher})"),
Self::Kind(matcher, _) => write!(f, "kind({matcher})"),
Self::Binary(matcher, _) => write!(f, "binary({matcher})"),
Self::BinaryId(matcher, _) => write!(f, "binary_id({matcher})"),
Self::Platform(platform, _) => write!(f, "platform({platform})"),
Self::Test(matcher, _) => write!(f, "test({matcher})"),
Self::Default(_) => write!(f, "default()"),
Self::All => write!(f, "all()"),
Self::None => write!(f, "none()"),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ParsedExpr<S = SourceSpan> {
Not(NotOperator, Box<ParsedExpr<S>>),
Union(OrOperator, Box<ParsedExpr<S>>, Box<ParsedExpr<S>>),
Intersection(AndOperator, Box<ParsedExpr<S>>, Box<ParsedExpr<S>>),
Difference(DifferenceOperator, Box<ParsedExpr<S>>, Box<ParsedExpr<S>>),
Parens(Box<ParsedExpr<S>>),
Set(SetDef<S>),
}
impl ParsedExpr {
pub fn parse(input: &str) -> Result<Self, Vec<ParseSingleError>> {
let mut errors = Vec::new();
let span = new_span(input, &mut errors);
match parse(span).unwrap() {
ExprResult::Valid(expr) => Ok(expr),
ExprResult::Error => Err(errors),
}
}
fn boxed(self) -> Box<Self> {
Box::new(self)
}
fn not(self, op: NotOperator) -> Self {
ParsedExpr::Not(op, self.boxed())
}
fn union(op: OrOperator, expr_1: Self, expr_2: Self) -> Self {
ParsedExpr::Union(op, expr_1.boxed(), expr_2.boxed())
}
fn intersection(op: AndOperator, expr_1: Self, expr_2: Self) -> Self {
ParsedExpr::Intersection(op, expr_1.boxed(), expr_2.boxed())
}
fn difference(op: DifferenceOperator, expr_1: Self, expr_2: Self) -> Self {
ParsedExpr::Difference(op, expr_1.boxed(), expr_2.boxed())
}
fn parens(self) -> Self {
ParsedExpr::Parens(self.boxed())
}
#[cfg(test)]
fn all() -> ParsedExpr {
ParsedExpr::Set(SetDef::All)
}
#[cfg(test)]
fn none() -> ParsedExpr {
ParsedExpr::Set(SetDef::None)
}
#[cfg(test)]
fn drop_source_span(self) -> ParsedExpr<()> {
match self {
Self::Not(op, expr) => ParsedExpr::Not(op, Box::new(expr.drop_source_span())),
Self::Union(op, a, b) => ParsedExpr::Union(
op,
Box::new(a.drop_source_span()),
Box::new(b.drop_source_span()),
),
Self::Intersection(op, a, b) => ParsedExpr::Intersection(
op,
Box::new(a.drop_source_span()),
Box::new(b.drop_source_span()),
),
Self::Difference(op, a, b) => ParsedExpr::Difference(
op,
Box::new(a.drop_source_span()),
Box::new(b.drop_source_span()),
),
Self::Parens(a) => ParsedExpr::Parens(Box::new(a.drop_source_span())),
Self::Set(set) => ParsedExpr::Set(set.drop_source_span()),
}
}
}
impl<S> fmt::Display for ParsedExpr<S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Not(op, expr) => write!(f, "{op} {expr}"),
Self::Union(op, expr_1, expr_2) => write!(f, "{expr_1} {op} {expr_2}"),
Self::Intersection(op, expr_1, expr_2) => write!(f, "{expr_1} {op} {expr_2}"),
Self::Difference(op, expr_1, expr_2) => write!(f, "{expr_1} {op} {expr_2}"),
Self::Parens(expr) => write!(f, "({expr})"),
Self::Set(set) => write!(f, "{set}"),
}
}
}
pub(crate) enum ExprResult {
Valid(ParsedExpr),
Error,
}
impl ExprResult {
fn combine(self, op: impl FnOnce(ParsedExpr, ParsedExpr) -> ParsedExpr, other: Self) -> Self {
match (self, other) {
(Self::Valid(expr_1), Self::Valid(expr_2)) => Self::Valid(op(expr_1, expr_2)),
_ => Self::Error,
}
}
fn negate(self, op: NotOperator) -> Self {
match self {
Self::Valid(expr) => Self::Valid(expr.not(op)),
_ => Self::Error,
}
}
fn parens(self) -> Self {
match self {
Self::Valid(expr) => Self::Valid(expr.parens()),
_ => Self::Error,
}
}
}
enum SpanLength {
Unknown,
Exact(usize),
Offset(isize, usize),
}
fn expect_inner<'a, F, T>(
mut parser: F,
make_err: fn(SourceSpan) -> ParseSingleError,
limit: SpanLength,
) -> impl Parser<Span<'a>, Option<T>, Error>
where
F: Parser<Span<'a>, T, Error>,
{
move |input: &mut _| match parser.parse_next(input) {
Ok(out) => Ok(Some(out)),
Err(winnow::error::ErrMode::Backtrack(_)) | Err(winnow::error::ErrMode::Cut(_)) => {
let fragment_start = input.location();
let fragment_length = input.slice_len();
let span = match limit {
SpanLength::Unknown => (fragment_start, fragment_length).into(),
SpanLength::Exact(x) => (fragment_start, x.min(fragment_length)).into(),
SpanLength::Offset(offset, x) => {
let effective_start = fragment_start.saturating_add_signed(offset);
let effective_end = effective_start + fragment_length;
let len = (effective_end - effective_start).min(x);
(effective_start, len).into()
}
};
let err = make_err(span);
input.state.report_error(err);
Ok(None)
}
Err(err) => Err(err),
}
}
fn expect<'a, F, T>(
parser: F,
make_err: fn(SourceSpan) -> ParseSingleError,
) -> impl Parser<Span<'a>, Option<T>, Error>
where
F: Parser<Span<'a>, T, Error>,
{
expect_inner(parser, make_err, SpanLength::Unknown)
}
fn expect_n<'a, F, T>(
parser: F,
make_err: fn(SourceSpan) -> ParseSingleError,
limit: SpanLength,
) -> impl Parser<Span<'a>, Option<T>, Error>
where
F: Parser<Span<'a>, T, Error>,
{
expect_inner(parser, make_err, limit)
}
fn expect_char<'a>(
c: char,
make_err: fn(SourceSpan) -> ParseSingleError,
) -> impl Parser<Span<'a>, Option<char>, Error> {
expect_inner(ws(c), make_err, SpanLength::Exact(0))
}
fn silent_expect<'a, F, T>(mut parser: F) -> impl Parser<Span<'a>, Option<T>, Error>
where
F: Parser<Span<'a>, T, Error>,
{
move |input: &mut _| match parser.parse_next(input) {
Ok(out) => Ok(Some(out)),
Err(winnow::error::ErrMode::Backtrack(_)) | Err(winnow::error::ErrMode::Cut(_)) => Ok(None),
Err(err) => Err(err),
}
}
fn ws<'a, T, P: Parser<Span<'a>, T, Error>>(mut inner: P) -> impl Parser<Span<'a>, T, Error> {
move |input: &mut Span<'a>| {
let start = input.checkpoint();
() = repeat(
0..,
alt((
' '.void(),
line_ending.void(),
)),
)
.parse_next(input)?;
match inner.parse_next(input) {
Ok(res) => Ok(res),
Err(winnow::error::ErrMode::Backtrack(err)) => {
input.reset(&start);
Err(winnow::error::ErrMode::Backtrack(err))
}
Err(winnow::error::ErrMode::Cut(err)) => {
input.reset(&start);
Err(winnow::error::ErrMode::Cut(err))
}
Err(err) => Err(err),
}
}
}
fn parse_matcher_text<'i>(input: &mut Span<'i>) -> PResult<Option<String>> {
trace("parse_matcher_text", |input: &mut Span<'i>| {
let res = match expect(
unicode_string::parse_string,
ParseSingleError::InvalidString,
)
.parse_next(input)
{
Ok(res) => res.flatten(),
Err(_) => unreachable!(),
};
if res.as_ref().map(|s| s.is_empty()).unwrap_or(false) {
let start = input.location();
input
.state
.report_error(ParseSingleError::InvalidString((start..0).into()));
}
Ok(res)
})
.parse_next(input)
}
fn parse_contains_matcher(input: &mut Span<'_>) -> PResult<Option<NameMatcher>> {
trace(
"parse_contains_matcher",
preceded('~', parse_matcher_text).map(|res: Option<String>| {
res.map(|value| NameMatcher::Contains {
value,
implicit: false,
})
}),
)
.parse_next(input)
}
fn parse_equal_matcher(input: &mut Span<'_>) -> PResult<Option<NameMatcher>> {
trace(
"parse_equal_matcher",
ws(
preceded('=', parse_matcher_text).map(|res: Option<String>| {
res.map(|value| NameMatcher::Equal {
value,
implicit: false,
})
}),
),
)
.parse_next(input)
}
fn parse_regex_inner(input: &mut Span<'_>) -> PResult<String> {
trace("parse_regex_inner", |input: &mut _| {
enum Frag<'a> {
Literal(&'a str),
Escape(char),
}
let parse_escape = alt((r"\/".value('/'), '\\')).map(Frag::Escape);
let parse_literal = take_till(1.., ('\\', '/'))
.verify(|s: &str| !s.is_empty())
.map(|s: &str| Frag::Literal(s));
let parse_frag = alt((parse_escape, parse_literal));
let res = repeat(0.., parse_frag)
.fold(String::new, |mut string, frag| {
match frag {
Frag::Escape(c) => string.push(c),
Frag::Literal(s) => string.push_str(s),
}
string
})
.parse_next(input)?;
let _ = peek('/').parse_next(input)?;
Ok(res)
})
.parse_next(input)
}
pub(crate) struct DisplayParsedRegex<'a>(pub(crate) &'a regex::Regex);
impl fmt::Display for DisplayParsedRegex<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let regex = self.0.as_str();
let mut escaped = false;
for c in regex.chars() {
if escaped {
escaped = false;
write!(f, "{c}")?;
} else if c == '\\' {
escaped = true;
write!(f, "{c}")?;
} else if c == '/' {
write!(f, "\\/")?;
} else {
write!(f, "{c}")?;
}
}
Ok(())
}
}
fn parse_regex<'i>(input: &mut Span<'i>) -> PResult<Option<NameMatcher>> {
trace("parse_regex", |input: &mut Span<'i>| {
let start = input.checkpoint();
let res = match parse_regex_inner.parse_next(input) {
Ok(res) => res,
Err(_) => {
input.reset(&start);
match take_till::<_, _, Error>(0.., ')').parse_next(input) {
Ok(_) => {
let start = input.location();
let err = ParseSingleError::ExpectedCloseRegex((start, 0).into());
input.state.report_error(err);
return Ok(None);
}
Err(_) => unreachable!(),
}
}
};
match regex::Regex::new(&res).map(NameMatcher::Regex) {
Ok(res) => Ok(Some(res)),
Err(_) => {
let end = input.checkpoint();
input.reset(&start);
let start = input.location();
input.reset(&end);
let end = input.location();
let err = ParseSingleError::invalid_regex(&res, start, end);
input.state.report_error(err);
Ok(None)
}
}
})
.parse_next(input)
}
fn parse_regex_matcher(input: &mut Span<'_>) -> PResult<Option<NameMatcher>> {
trace(
"parse_regex_matcher",
ws(delimited('/', parse_regex, silent_expect(ws('/')))),
)
.parse_next(input)
}
fn parse_glob_matcher(input: &mut Span<'_>) -> PResult<Option<NameMatcher>> {
trace(
"parse_glob_matcher",
ws(preceded('#', glob::parse_glob(false))),
)
.parse_next(input)
}
fn set_matcher<'a>(
default_matcher: DefaultMatcher,
) -> impl Parser<Span<'a>, Option<NameMatcher>, Error> {
ws(alt((
parse_regex_matcher,
parse_glob_matcher,
parse_equal_matcher,
parse_contains_matcher,
default_matcher.into_parser(),
)))
}
fn recover_unexpected_comma<'i>(input: &mut Span<'i>) -> PResult<()> {
trace("recover_unexpected_comma", |input: &mut Span<'i>| {
let start = input.checkpoint();
match peek(ws(',')).parse_next(input) {
Ok(_) => {
let pos = input.location();
input
.state
.report_error(ParseSingleError::UnexpectedComma((pos..0).into()));
match take_till::<_, _, Error>(0.., ')').parse_next(input) {
Ok(_) => Ok(()),
Err(_) => unreachable!(),
}
}
Err(_) => {
input.reset(&start);
Ok(())
}
}
})
.parse_next(input)
}
fn nullary_set_def<'a>(
name: &'static str,
make_set: fn(SourceSpan) -> SetDef,
) -> impl Parser<Span<'a>, Option<SetDef>, Error> {
move |i: &mut Span<'_>| {
let start = i.location();
let _ = literal(name).parse_next(i)?;
let _ = expect_char('(', ParseSingleError::ExpectedOpenParenthesis).parse_next(i)?;
let err_loc = i.location();
match take_till::<_, _, Error>(0.., ')').parse_next(i) {
Ok(res) => {
if !res.trim().is_empty() {
let span = (err_loc, res.len()).into();
let err = ParseSingleError::UnexpectedArgument(span);
i.state.report_error(err);
}
}
Err(_) => unreachable!(),
};
let _ = expect_char(')', ParseSingleError::ExpectedCloseParenthesis).parse_next(i)?;
let end = i.location();
Ok(Some(make_set((start, end - start).into())))
}
}
#[derive(Copy, Clone, Debug)]
enum DefaultMatcher {
Equal,
Contains,
Glob,
}
impl DefaultMatcher {
fn into_parser<'a>(self) -> impl Parser<Span<'a>, Option<NameMatcher>, Error> {
move |input: &mut _| match self {
Self::Equal => parse_matcher_text
.map(|res: Option<String>| res.map(NameMatcher::implicit_equal))
.parse_next(input),
Self::Contains => parse_matcher_text
.map(|res: Option<String>| res.map(NameMatcher::implicit_contains))
.parse_next(input),
Self::Glob => glob::parse_glob(true).parse_next(input),
}
}
}
fn unary_set_def<'a>(
name: &'static str,
default_matcher: DefaultMatcher,
make_set: fn(NameMatcher, SourceSpan) -> SetDef,
) -> impl Parser<Span<'a>, Option<SetDef>, Error> {
move |i: &mut _| {
let _ = literal(name).parse_next(i)?;
let _ = expect_char('(', ParseSingleError::ExpectedOpenParenthesis).parse_next(i)?;
let start = i.location();
let res = set_matcher(default_matcher).parse_next(i)?;
let end = i.location();
recover_unexpected_comma.parse_next(i)?;
let _ = expect_char(')', ParseSingleError::ExpectedCloseParenthesis).parse_next(i)?;
Ok(res.map(|matcher| make_set(matcher, (start, end - start).into())))
}
}
fn platform_def(i: &mut Span<'_>) -> PResult<Option<SetDef>> {
let _ = "platform".parse_next(i)?;
let _ = expect_char('(', ParseSingleError::ExpectedOpenParenthesis).parse_next(i)?;
let start = i.location();
let res = ws(parse_matcher_text).parse_next(i)?;
let end = i.location();
recover_unexpected_comma.parse_next(i)?;
let _ = expect_char(')', ParseSingleError::ExpectedCloseParenthesis).parse_next(i)?;
let platform = match res.as_deref().map(|res| res.trim()) {
Some("host") => Some(BuildPlatform::Host),
Some("target") => Some(BuildPlatform::Target),
Some(_) => {
i.state
.report_error(ParseSingleError::InvalidPlatformArgument(
(start, end - start).into(),
));
None
}
None => {
None
}
};
Ok(platform.map(|platform| SetDef::Platform(platform, (start, end - start).into())))
}
fn parse_set_def(input: &mut Span<'_>) -> PResult<Option<SetDef>> {
trace(
"parse_set_def",
ws(alt((
unary_set_def("package", DefaultMatcher::Glob, SetDef::Package),
unary_set_def("deps", DefaultMatcher::Glob, SetDef::Deps),
unary_set_def("rdeps", DefaultMatcher::Glob, SetDef::Rdeps),
unary_set_def("kind", DefaultMatcher::Equal, SetDef::Kind),
unary_set_def("binary_id", DefaultMatcher::Glob, SetDef::BinaryId),
unary_set_def("binary", DefaultMatcher::Glob, SetDef::Binary),
unary_set_def("test", DefaultMatcher::Contains, SetDef::Test),
platform_def,
nullary_set_def("default", SetDef::Default),
nullary_set_def("all", |_| SetDef::All),
nullary_set_def("none", |_| SetDef::None),
))),
)
.parse_next(input)
}
fn expect_expr<'a, P: Parser<Span<'a>, ExprResult, Error>>(
inner: P,
) -> impl Parser<Span<'a>, ExprResult, Error> {
expect(inner, ParseSingleError::ExpectedExpr).map(|res| res.unwrap_or(ExprResult::Error))
}
fn parse_parentheses_expr(input: &mut Span<'_>) -> PResult<ExprResult> {
trace(
"parse_parentheses_expr",
delimited(
'(',
expect_expr(parse_expr),
expect_char(')', ParseSingleError::ExpectedCloseParenthesis),
)
.map(|expr| expr.parens()),
)
.parse_next(input)
}
fn parse_basic_expr(input: &mut Span<'_>) -> PResult<ExprResult> {
trace(
"parse_basic_expr",
ws(alt((
parse_set_def.map(|set| {
set.map(|set| ExprResult::Valid(ParsedExpr::Set(set)))
.unwrap_or(ExprResult::Error)
}),
parse_expr_not,
parse_parentheses_expr,
))),
)
.parse_next(input)
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[cfg_attr(
any(test, feature = "internal-testing"),
derive(test_strategy::Arbitrary)
)]
pub enum NotOperator {
LiteralNot,
Exclamation,
}
impl fmt::Display for NotOperator {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
NotOperator::LiteralNot => f.write_str("not"),
NotOperator::Exclamation => f.write_str("!"),
}
}
}
fn parse_expr_not(input: &mut Span<'_>) -> PResult<ExprResult> {
trace(
"parse_expr_not",
(
alt((
"not ".value(NotOperator::LiteralNot),
'!'.value(NotOperator::Exclamation),
)),
expect_expr(ws(parse_basic_expr)),
)
.map(|(op, expr)| expr.negate(op)),
)
.parse_next(input)
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[cfg_attr(
any(test, feature = "internal-testing"),
derive(test_strategy::Arbitrary)
)]
pub enum OrOperator {
LiteralOr,
Pipe,
Plus,
}
impl fmt::Display for OrOperator {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
OrOperator::LiteralOr => f.write_str("or"),
OrOperator::Pipe => f.write_str("|"),
OrOperator::Plus => f.write_str("+"),
}
}
}
fn parse_expr(input: &mut Span<'_>) -> PResult<ExprResult> {
trace("parse_expr", |input: &mut _| {
let expr = expect_expr(parse_and_or_difference_expr).parse_next(input)?;
let ops = repeat(
0..,
(parse_or_operator, expect_expr(parse_and_or_difference_expr)),
)
.fold(Vec::new, |mut ops, (op, expr)| {
ops.push((op, expr));
ops
})
.parse_next(input)?;
let expr = ops.into_iter().fold(expr, |expr_1, (op, expr_2)| {
if let Some(op) = op {
expr_1.combine(
|expr_1, expr_2| ParsedExpr::union(op, expr_1, expr_2),
expr_2,
)
} else {
ExprResult::Error
}
});
Ok(expr)
})
.parse_next(input)
}
fn parse_or_operator<'i>(input: &mut Span<'i>) -> PResult<Option<OrOperator>> {
trace(
"parse_or_operator",
ws(alt((
|input: &mut Span<'i>| {
let start = input.location();
let op = alt(("||", "OR ")).parse_next(input)?;
let length = op.len();
let err = ParseSingleError::InvalidOrOperator((start, length).into());
input.state.report_error(err);
Ok(None)
},
"or ".value(Some(OrOperator::LiteralOr)),
'|'.value(Some(OrOperator::Pipe)),
'+'.value(Some(OrOperator::Plus)),
))),
)
.parse_next(input)
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[cfg_attr(
any(test, feature = "internal-testing"),
derive(test_strategy::Arbitrary)
)]
pub enum AndOperator {
LiteralAnd,
Ampersand,
}
impl fmt::Display for AndOperator {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AndOperator::LiteralAnd => f.write_str("and"),
AndOperator::Ampersand => f.write_str("&"),
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[cfg_attr(
any(test, feature = "internal-testing"),
derive(test_strategy::Arbitrary)
)]
pub enum DifferenceOperator {
Minus,
}
impl fmt::Display for DifferenceOperator {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
DifferenceOperator::Minus => f.write_str("-"),
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum AndOrDifferenceOperator {
And(AndOperator),
Difference(DifferenceOperator),
}
fn parse_and_or_difference_expr(input: &mut Span<'_>) -> PResult<ExprResult> {
trace("parse_and_or_difference_expr", |input: &mut _| {
let expr = expect_expr(parse_basic_expr).parse_next(input)?;
let ops = repeat(
0..,
(
parse_and_or_difference_operator,
expect_expr(parse_basic_expr),
),
)
.fold(Vec::new, |mut ops, (op, expr)| {
ops.push((op, expr));
ops
})
.parse_next(input)?;
let expr = ops.into_iter().fold(expr, |expr_1, (op, expr_2)| match op {
Some(AndOrDifferenceOperator::And(op)) => expr_1.combine(
|expr_1, expr_2| ParsedExpr::intersection(op, expr_1, expr_2),
expr_2,
),
Some(AndOrDifferenceOperator::Difference(op)) => expr_1.combine(
|expr_1, expr_2| ParsedExpr::difference(op, expr_1, expr_2),
expr_2,
),
None => ExprResult::Error,
});
Ok(expr)
})
.parse_next(input)
}
fn parse_and_or_difference_operator<'i>(
input: &mut Span<'i>,
) -> PResult<Option<AndOrDifferenceOperator>> {
trace(
"parse_and_or_difference_operator",
ws(alt((
|input: &mut Span<'i>| {
let start = input.location();
let op = alt(("&&", "AND ")).parse_next(input)?;
let length = op.len();
let err = ParseSingleError::InvalidAndOperator((start, length).into());
input.state.report_error(err);
Ok(None)
},
"and ".value(Some(AndOrDifferenceOperator::And(AndOperator::LiteralAnd))),
'&'.value(Some(AndOrDifferenceOperator::And(AndOperator::Ampersand))),
'-'.value(Some(AndOrDifferenceOperator::Difference(
DifferenceOperator::Minus,
))),
))),
)
.parse_next(input)
}
pub(crate) fn parse(input: Span<'_>) -> Result<ExprResult, winnow::error::ErrMode<Error>> {
let (_, expr) = terminated(
parse_expr,
expect(ws(eof), ParseSingleError::ExpectedEndOfExpression),
)
.parse_peek(input)?;
Ok(expr)
}
#[cfg(test)]
mod tests {
use super::*;
#[track_caller]
fn parse_regex(input: &str) -> NameMatcher {
let mut errors = Vec::new();
let span = new_span(input, &mut errors);
parse_regex_matcher.parse_peek(span).unwrap().1.unwrap()
}
#[test]
fn test_parse_regex() {
assert_eq!(
NameMatcher::Regex(regex::Regex::new(r"some.*").unwrap()),
parse_regex(r"/some.*/")
);
assert_eq!(
NameMatcher::Regex(regex::Regex::new(r"a/a").unwrap()),
parse_regex(r"/a\/a/")
);
assert_eq!(
NameMatcher::Regex(regex::Regex::new(r"\w/a").unwrap()),
parse_regex(r"/\w\/a/")
);
assert_eq!(
NameMatcher::Regex(regex::Regex::new(r"\w\\/a").unwrap()),
parse_regex(r"/\w\\\/a/")
);
assert_eq!(
NameMatcher::Regex(regex::Regex::new(r"\p{Greek}\\/a").unwrap()),
parse_regex(r"/\p{Greek}\\\/a/")
);
}
#[track_caller]
fn parse_glob(input: &str) -> NameMatcher {
let mut errors = Vec::new();
let span = new_span(input, &mut errors);
let matcher = parse_glob_matcher
.parse_peek(span)
.unwrap_or_else(|error| {
panic!("for input {input}, parse_glob_matcher returned an error: {error}")
})
.1
.unwrap_or_else(|| {
panic!(
"for input {input}, parse_glob_matcher returned None \
(reported errors: {errors:?})"
)
});
if !errors.is_empty() {
panic!("for input {input}, parse_glob_matcher reported errors: {errors:?}");
}
matcher
}
fn make_glob_matcher(glob: &str, implicit: bool) -> NameMatcher {
NameMatcher::Glob {
glob: GenericGlob::new(glob.to_owned()).unwrap(),
implicit,
}
}
#[test]
fn test_parse_glob_matcher() {
#[track_caller]
fn assert_glob(input: &str, expected: &str) {
assert_eq!(
make_glob_matcher(expected, false),
parse_glob(input),
"expected matches actual for input {input:?}",
);
}
assert_glob(r"#something)", "something");
assert_glob(r"#something*)", "something*");
assert_glob(r"#something?)", "something?");
assert_glob(r"#something[abc])", "something[abc]");
assert_glob(r"#something[!abc])", "something[!abc]");
assert_glob(r"#something[a-c])", "something[a-c]");
assert_glob(r"#foobar\b)", "foobar\u{08}");
assert_glob(r"#foobar\\b)", "foobar\\b");
assert_glob(r"#foobar\))", "foobar)");
}
#[track_caller]
fn parse_set(input: &str) -> SetDef {
let mut errors = Vec::new();
let span = new_span(input, &mut errors);
parse_set_def.parse_peek(span).unwrap().1.unwrap()
}
macro_rules! assert_set_def {
($input: expr, $name:ident, $matches:expr) => {
assert!(matches!($input, SetDef::$name(x, _) if x == $matches));
};
}
#[test]
fn test_parse_name_matcher() {
assert_set_def!(
parse_set("test(~something)"),
Test,
NameMatcher::Contains {
value: "something".to_string(),
implicit: false,
}
);
assert_set_def!(
parse_set("test(=something)"),
Test,
NameMatcher::Equal {
value: "something".to_string(),
implicit: false,
}
);
assert_set_def!(
parse_set("test(/some.*/)"),
Test,
NameMatcher::Regex(regex::Regex::new("some.*").unwrap())
);
assert_set_def!(
parse_set("test(#something)"),
Test,
make_glob_matcher("something", false)
);
assert_set_def!(
parse_set("test(#something*)"),
Test,
make_glob_matcher("something*", false)
);
assert_set_def!(
parse_set(r"test(#something/[?])"),
Test,
make_glob_matcher("something/[?]", false)
);
assert_set_def!(
parse_set("test(something)"),
Test,
NameMatcher::Contains {
value: "something".to_string(),
implicit: true,
}
);
assert_set_def!(
parse_set("package(something)"),
Package,
make_glob_matcher("something", true)
);
assert_set_def!(
parse_set("test(~something)"),
Test,
NameMatcher::Contains {
value: "something".to_string(),
implicit: false,
}
);
assert_set_def!(
parse_set("test(~~something)"),
Test,
NameMatcher::Contains {
value: "~something".to_string(),
implicit: false,
}
);
assert_set_def!(
parse_set("test(~=something)"),
Test,
NameMatcher::Contains {
value: "=something".to_string(),
implicit: false,
}
);
assert_set_def!(
parse_set("test(~/something/)"),
Test,
NameMatcher::Contains {
value: "/something/".to_string(),
implicit: false,
}
);
assert_set_def!(
parse_set("test(~#something)"),
Test,
NameMatcher::Contains {
value: "#something".to_string(),
implicit: false,
}
);
assert_set_def!(
parse_set("test(=something)"),
Test,
NameMatcher::Equal {
value: "something".to_string(),
implicit: false,
}
);
assert_set_def!(
parse_set("test(=~something)"),
Test,
NameMatcher::Equal {
value: "~something".to_string(),
implicit: false,
}
);
assert_set_def!(
parse_set("test(==something)"),
Test,
NameMatcher::Equal {
value: "=something".to_string(),
implicit: false,
}
);
assert_set_def!(
parse_set("test(=/something/)"),
Test,
NameMatcher::Equal {
value: "/something/".to_string(),
implicit: false,
}
);
assert_set_def!(
parse_set("test(=#something)"),
Test,
NameMatcher::Equal {
value: "#something".to_string(),
implicit: false,
}
);
assert_set_def!(
parse_set("test(#~something)"),
Test,
make_glob_matcher("~something", false)
);
assert_set_def!(
parse_set("test(#=something)"),
Test,
make_glob_matcher("=something", false)
);
assert_set_def!(
parse_set("test(#/something/)"),
Test,
make_glob_matcher("/something/", false)
);
assert_set_def!(
parse_set("test(##something)"),
Test,
make_glob_matcher("#something", false)
);
}
#[test]
fn test_parse_name_matcher_quote() {
assert_set_def!(
parse_set(r"test(some'thing)"),
Test,
NameMatcher::Contains {
value: r"some'thing".to_string(),
implicit: true,
}
);
assert_set_def!(
parse_set(r"test(some(thing\))"),
Test,
NameMatcher::Contains {
value: r"some(thing)".to_string(),
implicit: true,
}
);
assert_set_def!(
parse_set(r"test(some \u{55})"),
Test,
NameMatcher::Contains {
value: r"some U".to_string(),
implicit: true,
}
);
}
#[test]
fn test_parse_set_def() {
assert_eq!(SetDef::All, parse_set("all()"));
assert_eq!(SetDef::All, parse_set(" all ( ) "));
assert_eq!(SetDef::None, parse_set("none()"));
assert_set_def!(
parse_set("package(=something)"),
Package,
NameMatcher::Equal {
value: "something".to_string(),
implicit: false,
}
);
assert_set_def!(
parse_set("deps(something)"),
Deps,
make_glob_matcher("something", true)
);
assert_set_def!(
parse_set("rdeps(something)"),
Rdeps,
make_glob_matcher("something", true)
);
assert_set_def!(
parse_set("test(something)"),
Test,
NameMatcher::Contains {
value: "something".to_string(),
implicit: true,
}
);
assert_set_def!(parse_set("platform(host)"), Platform, BuildPlatform::Host);
assert_set_def!(
parse_set("platform(target)"),
Platform,
BuildPlatform::Target
);
assert_set_def!(
parse_set("platform( host )"),
Platform,
BuildPlatform::Host
);
}
#[track_caller]
fn parse(input: &str) -> ParsedExpr {
match ParsedExpr::parse(input) {
Ok(expr) => expr,
Err(errors) => {
for single_error in &errors {
let report = miette::Report::new(single_error.clone())
.with_source_code(input.to_owned());
eprintln!("{report:?}");
}
panic!("Not a valid expression!")
}
}
}
#[test]
fn test_parse_expr_set() {
let expr = ParsedExpr::all();
assert_eq!(expr, parse("all()"));
assert_eq!(expr, parse(" all ( ) "));
assert_eq!(format!("{expr}"), "all()");
}
#[test]
fn test_parse_expr_not() {
let expr = ParsedExpr::all().not(NotOperator::LiteralNot);
assert_eq_both_ways(&expr, "not all()");
assert_eq!(expr, parse("not all()"));
let expr = ParsedExpr::all().not(NotOperator::Exclamation);
assert_eq_both_ways(&expr, "! all()");
assert_eq!(expr, parse("!all()"));
let expr = ParsedExpr::all()
.not(NotOperator::LiteralNot)
.not(NotOperator::LiteralNot);
assert_eq_both_ways(&expr, "not not all()");
}
#[test]
fn test_parse_expr_intersection() {
let expr = ParsedExpr::intersection(
AndOperator::LiteralAnd,
ParsedExpr::all(),
ParsedExpr::none(),
);
assert_eq_both_ways(&expr, "all() and none()");
assert_eq!(expr, parse("all()and none()"));
let expr = ParsedExpr::intersection(
AndOperator::Ampersand,
ParsedExpr::all(),
ParsedExpr::none(),
);
assert_eq_both_ways(&expr, "all() & none()");
assert_eq!(expr, parse("all()&none()"));
}
#[test]
fn test_parse_expr_union() {
let expr = ParsedExpr::union(OrOperator::LiteralOr, ParsedExpr::all(), ParsedExpr::none());
assert_eq_both_ways(&expr, "all() or none()");
assert_eq!(expr, parse("all()or none()"));
let expr = ParsedExpr::union(OrOperator::Pipe, ParsedExpr::all(), ParsedExpr::none());
assert_eq_both_ways(&expr, "all() | none()");
assert_eq!(expr, parse("all()|none()"));
let expr = ParsedExpr::union(OrOperator::Plus, ParsedExpr::all(), ParsedExpr::none());
assert_eq_both_ways(&expr, "all() + none()");
assert_eq!(expr, parse("all()+none()"));
}
#[test]
fn test_parse_expr_difference() {
let expr = ParsedExpr::difference(
DifferenceOperator::Minus,
ParsedExpr::all(),
ParsedExpr::none(),
);
assert_eq_both_ways(&expr, "all() - none()");
assert_eq!(expr, parse("all()-none()"));
}
#[test]
fn test_parse_expr_precedence() {
let expr = ParsedExpr::intersection(
AndOperator::LiteralAnd,
ParsedExpr::all().not(NotOperator::LiteralNot),
ParsedExpr::none(),
);
assert_eq_both_ways(&expr, "not all() and none()");
let expr = ParsedExpr::intersection(
AndOperator::LiteralAnd,
ParsedExpr::all(),
ParsedExpr::none().not(NotOperator::LiteralNot),
);
assert_eq_both_ways(&expr, "all() and not none()");
let expr = ParsedExpr::intersection(
AndOperator::Ampersand,
ParsedExpr::all(),
ParsedExpr::none(),
);
let expr = ParsedExpr::union(OrOperator::Pipe, expr, ParsedExpr::all());
assert_eq_both_ways(&expr, "all() & none() | all()");
let expr = ParsedExpr::intersection(
AndOperator::Ampersand,
ParsedExpr::none(),
ParsedExpr::all(),
);
let expr = ParsedExpr::union(OrOperator::Pipe, ParsedExpr::all(), expr);
assert_eq_both_ways(&expr, "all() | none() & all()");
let expr =
ParsedExpr::union(OrOperator::Pipe, ParsedExpr::all(), ParsedExpr::none()).parens();
let expr = ParsedExpr::intersection(AndOperator::Ampersand, expr, ParsedExpr::all());
assert_eq_both_ways(&expr, "(all() | none()) & all()");
let expr = ParsedExpr::intersection(
AndOperator::Ampersand,
ParsedExpr::none(),
ParsedExpr::all(),
)
.parens();
let expr = ParsedExpr::union(OrOperator::Pipe, ParsedExpr::all(), expr);
assert_eq_both_ways(&expr, "all() | (none() & all())");
let expr = ParsedExpr::difference(
DifferenceOperator::Minus,
ParsedExpr::all(),
ParsedExpr::none(),
);
let expr = ParsedExpr::intersection(AndOperator::Ampersand, expr, ParsedExpr::all());
assert_eq_both_ways(&expr, "all() - none() & all()");
let expr = ParsedExpr::intersection(
AndOperator::Ampersand,
ParsedExpr::all(),
ParsedExpr::none(),
);
let expr = ParsedExpr::difference(DifferenceOperator::Minus, expr, ParsedExpr::all());
assert_eq_both_ways(&expr, "all() & none() - all()");
let expr = ParsedExpr::intersection(
AndOperator::Ampersand,
ParsedExpr::none(),
ParsedExpr::all(),
)
.parens()
.not(NotOperator::LiteralNot);
assert_eq_both_ways(&expr, "not (none() & all())");
}
#[test]
fn test_parse_comma() {
let expr = ParsedExpr::Set(SetDef::Test(
NameMatcher::Contains {
value: "a,".to_string(),
implicit: false,
},
(5, 4).into(),
));
assert_eq_both_ways(&expr, r"test(~a\,)");
fn parse_future_syntax(
input: &mut Span<'_>,
) -> PResult<(Option<NameMatcher>, Option<NameMatcher>)> {
let _ = "something".parse_next(input)?;
let _ = '('.parse_next(input)?;
let n1 = set_matcher(DefaultMatcher::Contains).parse_next(input)?;
let _ = ws(',').parse_next(input)?;
let n2 = set_matcher(DefaultMatcher::Contains).parse_next(input)?;
let _ = ')'.parse_next(input)?;
Ok((n1, n2))
}
let mut errors = Vec::new();
let mut span = new_span("something(aa, bb)", &mut errors);
if parse_future_syntax.parse_next(&mut span).is_err() {
panic!("Failed to parse comma separated matchers");
}
}
#[track_caller]
fn parse_err(input: &str) -> Vec<ParseSingleError> {
let mut errors = Vec::new();
let span = new_span(input, &mut errors);
super::parse(span).unwrap();
errors
}
macro_rules! assert_error {
($error:ident, $name:ident, $start:literal, $end:literal) => {{
let matches = matches!($error, ParseSingleError::$name(span) if span == ($start, $end).into());
assert!(
matches,
"expected: {:?}, actual: error: {:?}",
ParseSingleError::$name(($start, $end).into()),
$error,
);
}};
}
#[test]
fn test_invalid_and_operator() {
let src = "all() && none()";
let mut errors = parse_err(src);
assert_eq!(1, errors.len());
let error = errors.remove(0);
assert_error!(error, InvalidAndOperator, 6, 2);
let src = "all() AND none()";
let mut errors = parse_err(src);
assert_eq!(1, errors.len());
let error = errors.remove(0);
assert_error!(error, InvalidAndOperator, 6, 4);
}
#[test]
fn test_invalid_or_operator() {
let src = "all() || none()";
let mut errors = parse_err(src);
assert_eq!(1, errors.len());
let error = errors.remove(0);
assert_error!(error, InvalidOrOperator, 6, 2);
let src = "all() OR none()";
let mut errors = parse_err(src);
assert_eq!(1, errors.len());
let error = errors.remove(0);
assert_error!(error, InvalidOrOperator, 6, 3);
}
#[test]
fn test_missing_close_parentheses() {
let src = "all(";
let mut errors = parse_err(src);
assert_eq!(1, errors.len());
let error = errors.remove(0);
assert_error!(error, ExpectedCloseParenthesis, 4, 0);
}
#[test]
fn test_missing_open_parentheses() {
let src = "all)";
let mut errors = parse_err(src);
assert_eq!(1, errors.len());
let error = errors.remove(0);
assert_error!(error, ExpectedOpenParenthesis, 3, 0);
}
#[test]
fn test_missing_parentheses() {
let src = "all";
let mut errors = parse_err(src);
assert_eq!(2, errors.len());
let error = errors.remove(0);
assert_error!(error, ExpectedOpenParenthesis, 3, 0);
let error = errors.remove(0);
assert_error!(error, ExpectedCloseParenthesis, 3, 0);
}
#[test]
fn test_invalid_escapes() {
let src = r"package(foobar\$\#\@baz)";
let mut errors = parse_err(src);
assert_eq!(3, errors.len());
let error = errors.remove(0);
assert_error!(error, InvalidEscapeCharacter, 14, 2);
let error = errors.remove(0);
assert_error!(error, InvalidEscapeCharacter, 16, 2);
let error = errors.remove(0);
assert_error!(error, InvalidEscapeCharacter, 18, 2);
}
#[test]
fn test_invalid_regex() {
let src = "package(/)/)";
let mut errors = parse_err(src);
assert_eq!(1, errors.len());
let error = errors.remove(0);
assert!(
matches!(error, ParseSingleError::InvalidRegex { span, .. } if span == (9, 1).into())
);
let src = "package(/foo(ab/)";
let mut errors = parse_err(src);
assert_eq!(1, errors.len());
let error = errors.remove(0);
let (span, message) = match error {
ParseSingleError::InvalidRegex { span, message } => (span, message),
other => panic!("expected invalid regex with details, found {other}"),
};
assert_eq!(span, (12, 1).into(), "span matches");
assert_eq!(message, "unclosed group");
}
#[test]
fn test_invalid_glob() {
let src = "package(#)";
let mut errors = parse_err(src);
assert_eq!(1, errors.len());
let error = errors.remove(0);
assert_error!(error, InvalidString, 9, 0);
let src = "package(#foo[)";
let mut errors = parse_err(src);
assert_eq!(1, errors.len());
let error = errors.remove(0);
let (span, error) = match error {
ParseSingleError::InvalidGlob { span, error } => (span, error),
other => panic!("expected InvalidGlob with details, found {other}"),
};
assert_eq!(span, (9, 4).into(), "span matches");
assert_eq!(error.to_string(), "unclosed character class; missing ']'");
}
#[test]
fn test_invalid_platform() {
let src = "platform(foo)";
let mut errors = parse_err(src);
assert_eq!(1, errors.len());
let error = errors.remove(0);
assert_error!(error, InvalidPlatformArgument, 9, 3);
let src = "platform( bar\\t)";
let mut errors = parse_err(src);
assert_eq!(1, errors.len());
let error = errors.remove(0);
assert_error!(error, InvalidPlatformArgument, 9, 8);
}
#[test]
fn test_missing_close_regex() {
let src = "package(/aaa)";
let mut errors = parse_err(src);
assert_eq!(1, errors.len());
let error = errors.remove(0);
assert_error!(error, ExpectedCloseRegex, 12, 0);
}
#[test]
fn test_unexpected_argument() {
let src = "all(aaa)";
let mut errors = parse_err(src);
assert_eq!(1, errors.len());
let error = errors.remove(0);
assert_error!(error, UnexpectedArgument, 4, 3);
}
#[test]
fn test_expected_expr() {
let src = "all() + ";
let mut errors = parse_err(src);
assert_eq!(1, errors.len());
let error = errors.remove(0);
assert_error!(error, ExpectedExpr, 7, 1);
}
#[test]
fn test_expected_eof() {
let src = "all() blabla";
let mut errors = parse_err(src);
assert_eq!(1, errors.len());
let error = errors.remove(0);
assert_error!(error, ExpectedEndOfExpression, 5, 7);
}
#[test]
fn test_missing_argument() {
let src = "test()";
let mut errors = parse_err(src);
assert_eq!(1, errors.len());
let error = errors.remove(0);
assert_error!(error, InvalidString, 5, 0);
}
#[test]
fn test_unexpected_comma() {
let src = "test(aa, )";
let mut errors = parse_err(src);
assert_eq!(1, errors.len());
let error = errors.remove(0);
assert_error!(error, UnexpectedComma, 7, 0);
}
#[test]
fn test_complex_error() {
let src = "all) + package(/not) - deps(expr none)";
let mut errors = parse_err(src);
assert_eq!(2, errors.len(), "{errors:?}");
let error = errors.remove(0);
assert_error!(error, ExpectedOpenParenthesis, 3, 0);
let error = errors.remove(0);
assert_error!(error, ExpectedCloseRegex, 19, 0);
}
#[test_strategy::proptest]
fn proptest_expr_roundtrip(#[strategy(ParsedExpr::strategy())] expr: ParsedExpr<()>) {
let expr_string = expr.to_string();
eprintln!("expr string: {expr_string}");
let expr_2 = parse(&expr_string).drop_source_span();
assert_eq!(expr, expr_2, "exprs must roundtrip");
}
#[track_caller]
fn assert_eq_both_ways(expr: &ParsedExpr, string: &str) {
assert_eq!(expr, &parse(string));
assert_eq!(format!("{expr}"), string);
}
}