use crate::id::expression::{Operator, Term};
use crate::id::matcher::Matches;
mod builder;
mod group;
mod instruction;
pub use instruction::Instruction;
#[derive(Debug)]
pub struct Condition {
instructions: Box<[Instruction]>,
terms: Box<[Term]>,
}
impl Condition {
#[must_use]
pub fn satisfies(&self, matches: &Matches) -> bool {
let mut stack = 0u64;
for instruction in &self.instructions {
match instruction {
Instruction::Compare(operator, terms) => {
stack = (stack << 1)
| u64::from(match operator {
Operator::Any => matches.has_any(terms),
Operator::All => matches.has_all(terms),
Operator::Not => matches.has_any(terms),
});
}
Instruction::Combine(operator, arity) => {
let mask = (1 << arity) - 1;
let last = stack & mask;
stack >>= arity;
stack = (stack << 1)
| u64::from(match operator {
Operator::Any => last != 0,
Operator::All => last == mask,
Operator::Not => last == 0,
});
}
}
}
stack == 1
}
}
#[allow(clippy::must_use_candidate)]
impl Condition {
#[inline]
pub fn instructions(&self) -> &[Instruction] {
&self.instructions
}
#[inline]
pub fn terms(&self) -> &[Term] {
&self.terms
}
}
#[cfg(test)]
mod tests {
mod satisfies {
use crate::id::expression::{Expression, Result};
use crate::id::filter::Condition;
use crate::id::matcher::Matches;
use crate::selector;
#[test]
fn handles_any() -> Result {
let expr = Expression::any(|expr| {
expr.with(selector!(location = "**/*.jpg")?)?
.with(selector!(location = "**/*.png")?)
})?;
let condition = Condition::builder(expr).build();
for (matches, check) in [
(Matches::from_iter([]), false),
(Matches::from_iter([0]), true),
(Matches::from_iter([1]), true),
(Matches::from_iter([0, 1]), true),
(Matches::from_iter([0, 1, 2]), true),
(Matches::from_iter([2]), false),
] {
assert_eq!(condition.satisfies(&matches), check);
}
Ok(())
}
#[test]
fn handles_any_optimized() -> Result {
let expr = Expression::any(|expr| {
expr.with(selector!(location = "**/*.jpg")?)?
.with(selector!(location = "**/*.png")?)
})?;
let condition = Condition::builder(expr).optimize().build();
for (matches, check) in [
(Matches::from_iter([]), false),
(Matches::from_iter([0]), true),
(Matches::from_iter([1]), true),
(Matches::from_iter([0, 1]), true),
(Matches::from_iter([0, 1, 2]), true),
(Matches::from_iter([2]), false),
] {
assert_eq!(condition.satisfies(&matches), check);
}
Ok(())
}
#[test]
fn handles_all() -> Result {
let expr = Expression::all(|expr| {
expr.with(selector!(location = "**/*.md")?)?
.with(selector!(provider = "file")?)
})?;
let condition = Condition::builder(expr).build();
for (matches, check) in [
(Matches::from_iter([]), false),
(Matches::from_iter([0]), false),
(Matches::from_iter([1]), false),
(Matches::from_iter([0, 1]), true),
(Matches::from_iter([0, 1, 2]), true),
(Matches::from_iter([2]), false),
] {
assert_eq!(condition.satisfies(&matches), check);
}
Ok(())
}
#[test]
fn handles_all_optimized() -> Result {
let expr = Expression::all(|expr| {
expr.with(selector!(location = "**/*.md")?)?
.with(selector!(provider = "file")?)
})?;
let condition = Condition::builder(expr).optimize().build();
for (matches, check) in [
(Matches::from_iter([]), false),
(Matches::from_iter([0]), false),
(Matches::from_iter([1]), false),
(Matches::from_iter([0, 1]), true),
(Matches::from_iter([0, 1, 2]), true),
(Matches::from_iter([2]), false),
] {
assert_eq!(condition.satisfies(&matches), check);
}
Ok(())
}
#[test]
fn handles_not() -> Result {
let expr = Expression::not(|expr| {
expr.with(selector!(location = "**/*.jpg")?)?
.with(selector!(location = "**/*.png")?)
})?;
let condition = Condition::builder(expr).build();
for (matches, check) in [
(Matches::from_iter([]), true),
(Matches::from_iter([0]), false),
(Matches::from_iter([1]), false),
(Matches::from_iter([0, 1]), false),
(Matches::from_iter([0, 1, 2]), false),
(Matches::from_iter([2]), true),
] {
assert_eq!(condition.satisfies(&matches), check);
}
Ok(())
}
#[test]
fn handles_not_optimized() -> Result {
let expr = Expression::not(|expr| {
expr.with(selector!(location = "**/*.jpg")?)?
.with(selector!(location = "**/*.png")?)
})?;
let condition = Condition::builder(expr).optimize().build();
for (matches, check) in [
(Matches::from_iter([]), true),
(Matches::from_iter([0]), false),
(Matches::from_iter([1]), false),
(Matches::from_iter([0, 1]), false),
(Matches::from_iter([0, 1, 2]), false),
(Matches::from_iter([2]), true),
] {
assert_eq!(condition.satisfies(&matches), check);
}
Ok(())
}
#[test]
fn handles_all_any() -> Result {
let expr = Expression::all(|expr| {
expr.with(selector!(provider = "file")?)?
.with(Expression::any(|expr| {
expr.with(selector!(location = "**/*.jpg")?)?
.with(selector!(location = "**/*.png")?)
}))
})?;
let condition = Condition::builder(expr).build();
for (matches, check) in [
(Matches::from_iter([]), false),
(Matches::from_iter([0]), false),
(Matches::from_iter([1]), false),
(Matches::from_iter([0, 1]), true),
(Matches::from_iter([0, 2]), true),
(Matches::from_iter([0, 1, 3]), true),
(Matches::from_iter([1, 2]), false),
(Matches::from_iter([3]), false),
] {
assert_eq!(condition.satisfies(&matches), check);
}
Ok(())
}
#[test]
fn handles_all_any_optimized() -> Result {
let expr = Expression::all(|expr| {
expr.with(selector!(provider = "file")?)?
.with(Expression::any(|expr| {
expr.with(selector!(location = "**/*.jpg")?)?
.with(selector!(location = "**/*.png")?)
}))
})?;
let condition = Condition::builder(expr).optimize().build();
for (matches, check) in [
(Matches::from_iter([]), false),
(Matches::from_iter([0]), false),
(Matches::from_iter([1]), false),
(Matches::from_iter([0, 1]), true),
(Matches::from_iter([0, 2]), true),
(Matches::from_iter([0, 1, 3]), true),
(Matches::from_iter([1, 2]), false),
(Matches::from_iter([3]), false),
] {
assert_eq!(condition.satisfies(&matches), check);
}
Ok(())
}
#[test]
fn handles_all_any_not() -> Result {
let expr = Expression::all(|expr| {
expr.with(selector!(provider = "file")?)?
.with(Expression::any(|expr| {
expr.with(selector!(context = "docs")?)? .with(Expression::not(|expr| {
expr.with(selector!(location = "**/*.jpg")?)?
.with(selector!(location = "**/*.png")?)
}),
)
}))
})?;
let condition = Condition::builder(expr).build();
for (matches, check) in [
(Matches::from_iter([]), false),
(Matches::from_iter([0]), true),
(Matches::from_iter([1]), false),
(Matches::from_iter([0, 1]), true),
(Matches::from_iter([0, 2]), false),
(Matches::from_iter([0, 3]), false),
(Matches::from_iter([0, 1, 2]), true),
(Matches::from_iter([0, 1, 3]), true),
(Matches::from_iter([0, 4]), true),
(Matches::from_iter([4]), false),
] {
assert_eq!(condition.satisfies(&matches), check);
}
Ok(())
}
#[test]
fn handles_all_any_not_optimized() -> Result {
let expr = Expression::all(|expr| {
expr.with(selector!(provider = "file")?)?
.with(Expression::any(|expr| {
expr.with(selector!(context = "docs")?)? .with(Expression::not(|expr| {
expr.with(selector!(location = "**/*.jpg")?)?
.with(selector!(location = "**/*.png")?)
}),
)
}))
})?;
let condition = Condition::builder(expr).optimize().build();
for (matches, check) in [
(Matches::from_iter([]), false),
(Matches::from_iter([0]), true),
(Matches::from_iter([1]), false),
(Matches::from_iter([0, 1]), true),
(Matches::from_iter([0, 2]), false),
(Matches::from_iter([0, 3]), false),
(Matches::from_iter([0, 1, 2]), true),
(Matches::from_iter([0, 1, 3]), true),
(Matches::from_iter([0, 4]), true),
(Matches::from_iter([4]), false),
] {
assert_eq!(condition.satisfies(&matches), check);
}
Ok(())
}
}
}