aboutsummaryrefslogtreecommitdiff
path: root/src/filters/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/filters/mod.rs')
-rw-r--r--src/filters/mod.rs229
1 files changed, 229 insertions, 0 deletions
diff --git a/src/filters/mod.rs b/src/filters/mod.rs
new file mode 100644
index 0000000..162b6f8
--- /dev/null
+++ b/src/filters/mod.rs
@@ -0,0 +1,229 @@
+use std::{fmt, 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<Inclusion> 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<Inclusion> 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<bool> 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<dyn Filter>`.
+pub trait Filter<Early, Late>: Send + Sync + fmt::Debug {
+ /// 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)]
+struct Const(pub bool);
+
+impl<E, L> Filter<E, L> for Const {
+ fn filter_early(&self, _: &E) -> Inclusion {
+ self.0.into()
+ }
+
+ fn filter(&self, _: &L) -> bool {
+ self.0
+ }
+}
+
+/// Construct a `Filter` that always returns a fixed value.
+pub fn constant<E, L>(output: bool) -> Box<dyn Filter<E, L>> {
+ Box::new(Const(output))
+}
+
+struct AndFilter<E, L>(Box<dyn Filter<E, L>>, Box<dyn Filter<E, L>>);
+
+impl<E, L> Filter<E, L> for AndFilter<E, L> {
+ 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<E: 'static, L: 'static> ops::BitAnd<Box<dyn Filter<E, L>>> for Box<dyn Filter<E, L>> {
+ type Output = Box<dyn Filter<E, L>>;
+
+ fn bitand(self, rhs: Box<dyn Filter<E, L>>) -> Self::Output {
+ Box::new(AndFilter(self, rhs))
+ }
+}
+
+impl<E, L> fmt::Debug for AndFilter<E, L> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "({:?}) and ({:?})", self.0, self.1)
+ }
+}
+
+struct OrFilter<E, L>(Box<dyn Filter<E, L>>, Box<dyn Filter<E, L>>);
+
+impl<E, L> Filter<E, L> for OrFilter<E, L> {
+ 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<E: 'static, L: 'static> ops::BitOr<Box<dyn Filter<E, L>>> for Box<dyn Filter<E, L>> {
+ type Output = Box<dyn Filter<E, L>>;
+
+ fn bitor(self, rhs: Box<dyn Filter<E, L>>) -> Self::Output {
+ Box::new(OrFilter(self, rhs))
+ }
+}
+
+impl<E, L> fmt::Debug for OrFilter<E, L> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "({:?}) or ({:?})", self.0, self.1)
+ }
+}
+
+struct NotFilter<E, L>(Box<dyn Filter<E, L>>);
+
+impl<E, L> Filter<E, L> for NotFilter<E, L> {
+ 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<E: 'static, L: 'static> ops::Not for Box<dyn Filter<E, L>> {
+ type Output = Box<dyn Filter<E, L>>;
+
+ fn not(self) -> Self::Output {
+ Box::new(NotFilter(self))
+ }
+}
+
+impl<E, L> fmt::Debug for NotFilter<E, L> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "not ({:?})", self.0)
+ }
+}
+
+#[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);
+ }
+}