From ba491c8a5f6c8c2fa86b12dacf9d80f92da9168a Mon Sep 17 00:00:00 2001 From: Daniel Date: Fri, 17 Apr 2020 15:18:20 +0200 Subject: split off player filters and log filters As it turns out, we can easily re-use the existing Filter machinery to generalize over LogFilters (which operate on LogResults) and PlayerFilters (which operate on Players). The feature trait_aliases is not strictly needed but makes the function signatures a bit nicer and easier to read, and it reduces the chances of an error (e.g. by using Filter<&PartialEvtc, ...>). --- src/filters/mod.rs | 213 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 213 insertions(+) create mode 100644 src/filters/mod.rs (limited to 'src/filters/mod.rs') diff --git a/src/filters/mod.rs b/src/filters/mod.rs new file mode 100644 index 0000000..62dc04b --- /dev/null +++ b/src/filters/mod.rs @@ -0,0 +1,213 @@ +#![allow(clippy::new_ret_no_self)] +use std::ops; + +use num_derive::FromPrimitive; +use num_traits::FromPrimitive as _; + +pub mod log; +pub mod player; + +/// Early filtering result. +/// +/// This implements a [three-valued logic](https://en.wikipedia.org/wiki/Three-valued_logic), +/// similar to SQL's `NULL`. +#[derive(Debug, Copy, Clone, PartialEq, Eq, FromPrimitive)] +#[repr(i8)] +pub enum Inclusion { + /// The log should be included. + Include = 1, + /// The state is yet unknown. + Unknown = 0, + /// The log should be excluded. + Exclude = -1, +} + +impl ops::Not for Inclusion { + type Output = Self; + + fn not(self) -> Self::Output { + Inclusion::from_i8(-(self as i8)).unwrap() + } +} + +impl ops::BitAnd for Inclusion { + type Output = Self; + + fn bitand(self, rhs: Inclusion) -> Self::Output { + Inclusion::from_i8((self as i8).min(rhs as i8)).unwrap() + } +} + +impl ops::BitOr for Inclusion { + type Output = Self; + + fn bitor(self, rhs: Inclusion) -> Self::Output { + Inclusion::from_i8((self as i8).max(rhs as i8)).unwrap() + } +} + +impl From for Inclusion { + fn from(data: bool) -> Self { + if data { + Inclusion::Include + } else { + Inclusion::Exclude + } + } +} + +/// The main filter trait. +/// +/// Filters are usually handled as a `Box`. +pub trait Filter: Send + Sync { + /// Determine early (before processing all events) whether the log stands a chance to be + /// included. + /// + /// Note that you can return [Inclusion::Unkown] if this filter cannot determine yet a definite + /// answer. + fn filter_early(&self, _: &Early) -> Inclusion { + Inclusion::Unknown + } + + /// Return whether the log should be included, according to this filter. + fn filter(&self, _: &Late) -> bool; +} + +#[derive(Debug, Clone, Copy)] +pub struct Const(pub bool); + +impl Const { + pub fn new(output: bool) -> Box> { + Box::new(Const(output)) + } +} + +impl Filter for Const { + fn filter_early(&self, _: &E) -> Inclusion { + self.0.into() + } + + fn filter(&self, _: &L) -> bool { + self.0 + } +} + +struct AndFilter(Box>, Box>); + +impl Filter for AndFilter { + fn filter_early(&self, partial_evtc: &E) -> Inclusion { + let lhs = self.0.filter_early(partial_evtc); + // Short circuit behaviour + if lhs == Inclusion::Exclude { + Inclusion::Exclude + } else { + lhs & self.1.filter_early(partial_evtc) + } + } + + fn filter(&self, log: &L) -> bool { + self.0.filter(log) && self.1.filter(log) + } +} + +impl ops::BitAnd>> for Box> { + type Output = Box>; + + fn bitand(self, rhs: Box>) -> Self::Output { + Box::new(AndFilter(self, rhs)) + } +} + +struct OrFilter(Box>, Box>); + +impl Filter for OrFilter { + fn filter_early(&self, partial_evtc: &E) -> Inclusion { + let lhs = self.0.filter_early(partial_evtc); + // Short circuit behaviour + if lhs == Inclusion::Include { + Inclusion::Include + } else { + lhs | self.1.filter_early(partial_evtc) + } + } + + fn filter(&self, log: &L) -> bool { + self.0.filter(log) || self.1.filter(log) + } +} + +impl ops::BitOr>> for Box> { + type Output = Box>; + + fn bitor(self, rhs: Box>) -> Self::Output { + Box::new(OrFilter(self, rhs)) + } +} + +struct NotFilter(Box>); + +impl Filter for NotFilter { + fn filter_early(&self, partial_evtc: &E) -> Inclusion { + !self.0.filter_early(partial_evtc) + } + + fn filter(&self, log: &L) -> bool { + !self.0.filter(log) + } +} + +impl ops::Not for Box> { + type Output = Box>; + + fn not(self) -> Self::Output { + Box::new(NotFilter(self)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_inclusion_not() { + use Inclusion::*; + + assert_eq!(!Exclude, Include); + assert_eq!(!Include, Exclude); + assert_eq!(!Unknown, Unknown); + } + + #[test] + fn test_inclusion_and() { + use Inclusion::*; + + assert_eq!(Exclude & Exclude, Exclude); + assert_eq!(Exclude & Unknown, Exclude); + assert_eq!(Exclude & Include, Exclude); + + assert_eq!(Unknown & Exclude, Exclude); + assert_eq!(Unknown & Unknown, Unknown); + assert_eq!(Unknown & Include, Unknown); + + assert_eq!(Include & Exclude, Exclude); + assert_eq!(Include & Unknown, Unknown); + assert_eq!(Include & Include, Include); + } + + #[test] + fn test_inclusion_or() { + use Inclusion::*; + + assert_eq!(Exclude | Exclude, Exclude); + assert_eq!(Exclude | Unknown, Unknown); + assert_eq!(Exclude | Include, Include); + + assert_eq!(Unknown | Exclude, Unknown); + assert_eq!(Unknown | Unknown, Unknown); + assert_eq!(Unknown | Include, Include); + + assert_eq!(Include | Exclude, Include); + assert_eq!(Include | Unknown, Include); + assert_eq!(Include | Include, Include); + } +} -- cgit v1.2.3 From 7030224fd2a97b3551fdd47c43249e3a42341238 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 18 Apr 2020 15:10:59 +0200 Subject: make filters Debug It's nice if you can print out the filter tree for debugging, so we're requireing filters to be Debug now. --- src/filters/mod.rs | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) (limited to 'src/filters/mod.rs') diff --git a/src/filters/mod.rs b/src/filters/mod.rs index 62dc04b..525ff27 100644 --- a/src/filters/mod.rs +++ b/src/filters/mod.rs @@ -1,5 +1,5 @@ #![allow(clippy::new_ret_no_self)] -use std::ops; +use std::{ops, fmt}; use num_derive::FromPrimitive; use num_traits::FromPrimitive as _; @@ -59,7 +59,7 @@ impl From for Inclusion { /// The main filter trait. /// /// Filters are usually handled as a `Box`. -pub trait Filter: Send + Sync { +pub trait Filter: Send + Sync + fmt::Debug { /// Determine early (before processing all events) whether the log stands a chance to be /// included. /// @@ -118,6 +118,12 @@ impl ops::BitAnd>> for Box fmt::Debug for AndFilter { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "({:?}) and ({:?})", self.0, self.1) + } +} + struct OrFilter(Box>, Box>); impl Filter for OrFilter { @@ -144,6 +150,12 @@ impl ops::BitOr>> for Box fmt::Debug for OrFilter { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "({:?}) or ({:?})", self.0, self.1) + } +} + struct NotFilter(Box>); impl Filter for NotFilter { @@ -164,6 +176,12 @@ impl ops::Not for Box> { } } +impl fmt::Debug for NotFilter { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "not ({:?})", self.0) + } +} + #[cfg(test)] mod tests { use super::*; -- cgit v1.2.3 From 185a5b2f802f9d05c3eb40f807c0488f168c6661 Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 21 Apr 2020 13:59:28 +0200 Subject: better error outputs --- src/filters/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/filters/mod.rs') diff --git a/src/filters/mod.rs b/src/filters/mod.rs index 525ff27..3d0868b 100644 --- a/src/filters/mod.rs +++ b/src/filters/mod.rs @@ -1,5 +1,5 @@ #![allow(clippy::new_ret_no_self)] -use std::{ops, fmt}; +use std::{fmt, ops}; use num_derive::FromPrimitive; use num_traits::FromPrimitive as _; -- cgit v1.2.3 From 5dbea93266c3a30dac5ec6f5a7915d73a440f573 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 25 Apr 2020 13:14:30 +0200 Subject: use free functions instead of Filter::new Having a ::new on each of the filter types was a bit weird, especially because we returned Box instead of Self (and clippy rightfully complained). With this patch, we now have a bunch of normal functions, and we don't show to the outside how a filter is actually implemented (or what struct is behind it). --- src/filters/mod.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) (limited to 'src/filters/mod.rs') diff --git a/src/filters/mod.rs b/src/filters/mod.rs index 3d0868b..162b6f8 100644 --- a/src/filters/mod.rs +++ b/src/filters/mod.rs @@ -1,4 +1,3 @@ -#![allow(clippy::new_ret_no_self)] use std::{fmt, ops}; use num_derive::FromPrimitive; @@ -74,13 +73,7 @@ pub trait Filter: Send + Sync + fmt::Debug { } #[derive(Debug, Clone, Copy)] -pub struct Const(pub bool); - -impl Const { - pub fn new(output: bool) -> Box> { - Box::new(Const(output)) - } -} +struct Const(pub bool); impl Filter for Const { fn filter_early(&self, _: &E) -> Inclusion { @@ -92,6 +85,11 @@ impl Filter for Const { } } +/// Construct a `Filter` that always returns a fixed value. +pub fn constant(output: bool) -> Box> { + Box::new(Const(output)) +} + struct AndFilter(Box>, Box>); impl Filter for AndFilter { -- cgit v1.2.3