use crate::{
errors::{BannedPredicateReason, ParseSingleError},
expression::*,
parsing::{ParsedExpr, SetDef},
};
use guppy::{
graph::{DependsCache, PackageMetadata},
PackageId,
};
use miette::SourceSpan;
use recursion::CollapsibleExt;
use std::collections::HashSet;
pub(crate) fn compile(
expr: &ParsedExpr,
cx: &ParseContext<'_>,
) -> Result<CompiledExpr, Vec<ParseSingleError>> {
let mut errors = vec![];
check_banned_predicates(expr, cx.kind, &mut errors);
let in_workspace_packages: Vec<_> = cx
.graph
.resolve_workspace()
.packages(guppy::graph::DependencyDirection::Forward)
.collect();
let mut cache = cx.graph.new_depends_cache();
let expr = compile_expr(expr, &in_workspace_packages, &mut cache, &mut errors);
if errors.is_empty() {
Ok(expr)
} else {
Err(errors)
}
}
fn check_banned_predicates(
expr: &ParsedExpr,
kind: FiltersetKind,
errors: &mut Vec<ParseSingleError>,
) {
match kind {
FiltersetKind::Test => {}
FiltersetKind::DefaultFilter => {
Wrapped(expr).collapse_frames(|layer: ExprFrame<&SetDef, ()>| {
if let ExprFrame::Set(SetDef::Default(span)) = layer {
errors.push(ParseSingleError::BannedPredicate {
kind,
span: *span,
reason: BannedPredicateReason::InfiniteRecursion,
});
}
})
}
}
}
fn matching_packages(
matcher: &NameMatcher,
all_packages: &[PackageMetadata<'_>],
) -> HashSet<PackageId> {
all_packages
.iter()
.filter(|p| matcher.is_match(p.name()))
.map(|p| p.id().clone())
.collect()
}
fn dependencies_packages(
matcher: &NameMatcher,
all_packages: &[PackageMetadata<'_>],
cache: &mut DependsCache<'_>,
) -> HashSet<PackageId> {
let packages = all_packages
.iter()
.filter(|p| matcher.is_match(p.name()))
.map(|p| p.id());
let mut set = HashSet::new();
for id1 in packages {
for p2 in all_packages {
let id2 = p2.id();
if cache.depends_on(id1, id2).unwrap_or(false) {
set.insert(id2.clone());
}
}
}
set
}
fn rdependencies_packages(
matcher: &NameMatcher,
all_packages: &[PackageMetadata<'_>],
cache: &mut DependsCache<'_>,
) -> HashSet<PackageId> {
let packages = all_packages
.iter()
.filter(|p| matcher.is_match(p.name()))
.map(|p| p.id());
let mut set = HashSet::new();
for id1 in packages {
for p2 in all_packages {
let id2 = p2.id();
if cache.depends_on(id2, id1).unwrap_or(false) {
set.insert(id2.clone());
}
}
}
set
}
fn compile_set_def(
set: &SetDef,
packages: &[PackageMetadata<'_>],
cache: &mut DependsCache<'_>,
errors: &mut Vec<ParseSingleError>,
) -> FiltersetLeaf {
match set {
SetDef::Package(matcher, span) => FiltersetLeaf::Packages(expect_non_empty(
matching_packages(matcher, packages),
*span,
errors,
)),
SetDef::Deps(matcher, span) => FiltersetLeaf::Packages(expect_non_empty(
dependencies_packages(matcher, packages, cache),
*span,
errors,
)),
SetDef::Rdeps(matcher, span) => FiltersetLeaf::Packages(expect_non_empty(
rdependencies_packages(matcher, packages, cache),
*span,
errors,
)),
SetDef::Kind(matcher, span) => FiltersetLeaf::Kind(matcher.clone(), *span),
SetDef::Binary(matcher, span) => FiltersetLeaf::Binary(matcher.clone(), *span),
SetDef::BinaryId(matcher, span) => FiltersetLeaf::BinaryId(matcher.clone(), *span),
SetDef::Platform(platform, span) => FiltersetLeaf::Platform(*platform, *span),
SetDef::Test(matcher, span) => FiltersetLeaf::Test(matcher.clone(), *span),
SetDef::Default(_) => FiltersetLeaf::Default,
SetDef::All => FiltersetLeaf::All,
SetDef::None => FiltersetLeaf::None,
}
}
fn expect_non_empty(
packages: HashSet<PackageId>,
span: SourceSpan,
errors: &mut Vec<ParseSingleError>,
) -> HashSet<PackageId> {
if packages.is_empty() {
errors.push(ParseSingleError::NoPackageMatch(span));
}
packages
}
fn compile_expr(
expr: &ParsedExpr,
packages: &[PackageMetadata<'_>],
cache: &mut DependsCache<'_>,
errors: &mut Vec<ParseSingleError>,
) -> CompiledExpr {
use crate::expression::ExprFrame::*;
Wrapped(expr).collapse_frames(|layer: ExprFrame<&SetDef, CompiledExpr>| match layer {
Set(set) => CompiledExpr::Set(compile_set_def(set, packages, cache, errors)),
Not(expr) => CompiledExpr::Not(Box::new(expr)),
Union(expr_1, expr_2) => CompiledExpr::Union(Box::new(expr_1), Box::new(expr_2)),
Intersection(expr_1, expr_2) => {
CompiledExpr::Intersection(Box::new(expr_1), Box::new(expr_2))
}
Difference(expr_1, expr_2) => CompiledExpr::Intersection(
Box::new(expr_1),
Box::new(CompiledExpr::Not(Box::new(expr_2))),
),
Parens(expr_1) => expr_1,
})
}