diff --git a/src/lib.rs b/src/lib.rs index 4591df1..682a7df 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,7 +29,7 @@ pub use crate::{ extend_with::ExtendWith, filter_anti::FilterAnti, filter_with::FilterWith, - filters::{PrefixFilter, ValueFilter}, + filters::{passthrough, PrefixFilter, ValueFilter}, Leaper, Leapers, RelationLeaper, }, variable::Variable, diff --git a/src/test.rs b/src/test.rs index 9d5af35..d76c884 100644 --- a/src/test.rs +++ b/src/test.rs @@ -193,3 +193,29 @@ fn leapjoin_from_extend() { assert_eq!(variable.elements, vec![(2, 2), (2, 4)]); } + +#[test] +fn passthrough_leaper() { + let mut iteration = Iteration::new(); + + let variable = iteration.variable::<(u32, u32)>("variable"); + variable.extend((0..10).map(|i| (i, i))); + + while iteration.changed() { + variable.from_leapjoin( + &variable, + ( + crate::passthrough(), // Without this, the test would fail at runtime. + crate::PrefixFilter::from(|&(i, _)| i <= 20), + ), + |&(i, j), ()| (2*i, 2*j), + ); + } + + let variable = variable.complete(); + + let mut expected: Vec<_> = (0..10).map(|i| (i, i)).collect(); + expected.extend((10..20).filter_map(|i| (i%2 == 0).then(|| (i, i)))); + expected.extend((20..=40).filter_map(|i| (i%4 == 0).then(|| (i, i)))); + assert_eq!(&*variable, &expected); +} diff --git a/src/treefrog.rs b/src/treefrog.rs index 8a3b5f2..ab4ff36 100644 --- a/src/treefrog.rs +++ b/src/treefrog.rs @@ -190,6 +190,53 @@ pub(crate) mod filters { } } + pub struct Passthrough { + phantom: ::std::marker::PhantomData, + } + + impl Passthrough { + fn new() -> Self { + Passthrough { + phantom: ::std::marker::PhantomData, + } + } + } + + impl<'leap, Tuple> Leaper<'leap, Tuple, ()> for Passthrough { + /// Estimates the number of proposed values. + fn count(&mut self, _prefix: &Tuple) -> usize { + 1 + } + /// Populates `values` with proposed values. + fn propose(&mut self, _prefix: &Tuple, values: &mut Vec<&'leap ()>) { + values.push(&()) + } + /// Restricts `values` to proposed values. + fn intersect(&mut self, _prefix: &Tuple, _values: &mut Vec<&'leap ()>) { + // `Passthrough` never removes values (although if we're here it indicates that the user + // didn't need a `Passthrough` in the first place) + } + } + + /// Returns a leaper that proposes a single copy of each tuple from the main input. + /// + /// Use this when you don't need any "extend" leapers in a join, only "filter"s. For example, + /// in the following datalog rule, all terms in the second and third predicate are bound in the + /// first one (the "main input" to our leapjoin). + /// + /// ```prolog + /// error(loan, point) :- + /// origin_contains_loan_at(origin, loan, point), % main input + /// origin_live_at(origin, point), + /// loan_invalidated_at(loan, point). + /// ``` + /// + /// Without a passthrough leaper, neither the filter for `origin_live_at` nor the one for + /// `loan_invalidated_at` would propose any tuples, and the leapjoin would panic at runtime. + pub fn passthrough() -> Passthrough { + Passthrough::new() + } + /// A treefrog leaper based on a predicate of prefix and value. /// Use like `ValueFilter::from(|tuple, value| ...)`. The closure /// should return true if `value` ought to be retained. The