tytanic_filter/eval/
set.rs

1use std::fmt::Debug;
2use std::sync::Arc;
3
4use ecow::eco_vec;
5
6use super::Context;
7use super::Error;
8use super::Test;
9use super::TryFromValue;
10use super::Type;
11use super::Value;
12use crate::ast::Pat;
13
14/// The backing implementation for a [`Set`].
15type SetImpl<T> = Arc<dyn Fn(&Context<T>, &T) -> Result<bool, Error> + Send + Sync + 'static>;
16
17/// A test set, this can be used to check if a test is contained in it and is
18/// expected to be the top level value in an [`ExpressionFilter`][filter].
19///
20/// [filter]: crate::ExpressionFilter
21#[derive(Clone)]
22pub struct Set<T>(SetImpl<T>);
23
24impl<T> Set<T> {
25    /// Create a new set with the given implementation.
26    pub fn new<F>(f: F) -> Self
27    where
28        F: Fn(&Context<T>, &T) -> Result<bool, Error> + Send + Sync + 'static,
29    {
30        Self(Arc::new(f) as _)
31    }
32
33    /// Whether the given test is contained within this set.
34    pub fn contains(&self, ctx: &Context<T>, test: &T) -> Result<bool, Error> {
35        (self.0)(ctx, test)
36    }
37}
38
39impl<T> Debug for Set<T> {
40    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41        f.debug_tuple("Set").field(&..).finish()
42    }
43}
44
45impl<T: Test> Set<T> {
46    /// Construct a test set which contains all tests matching the given pattern.
47    ///
48    /// This is the test set created from pattern literals like `r:'foot-(\w-)+'`.
49    pub fn coerce_pat(pat: Pat) -> Set<T> {
50        Set::new(move |_, test: &T| Ok(pat.is_match(test.id())))
51    }
52}
53
54impl<T: 'static> Set<T> {
55    /// Construct a set which contains all tests _not_ contained in the given
56    /// set.
57    ///
58    /// This is the test set created by `!set`.
59    pub fn expr_comp(set: Set<T>) -> Self {
60        Self::new(move |ctx, test| Ok(!set.contains(ctx, test)?))
61    }
62
63    /// Construct a set which contains all tests which are contained in any of
64    /// the given sets.
65    ///
66    /// This is the test set created by `a | b`.
67    pub fn expr_union<I>(a: Set<T>, b: Set<T>, rest: I) -> Self
68    where
69        I: IntoIterator<Item = Set<T>>,
70    {
71        let sets: Vec<_> = [a, b].into_iter().chain(rest).collect();
72
73        Self::new(move |ctx, test| {
74            for set in &sets {
75                if set.contains(ctx, test)? {
76                    return Ok(true);
77                }
78            }
79
80            Ok(false)
81        })
82    }
83
84    /// Construct a set which contains all tests which are contained in all the
85    /// given sets.
86    ///
87    /// This is the test set created by `a & b`.
88    pub fn expr_inter<I>(a: Set<T>, b: Set<T>, rest: I) -> Self
89    where
90        I: IntoIterator<Item = Set<T>>,
91    {
92        let sets: Vec<_> = [a, b].into_iter().chain(rest).collect();
93
94        Self::new(move |ctx, test| {
95            for set in &sets {
96                if !set.contains(ctx, test)? {
97                    return Ok(false);
98                }
99            }
100
101            Ok(true)
102        })
103    }
104
105    /// Construct a set which contains all tests which are contained in the
106    /// first but not the second set.
107    ///
108    /// This is the test set created by `a ~ b` and is equivalent to `a & !b`.
109    pub fn expr_diff(a: Set<T>, b: Set<T>) -> Self {
110        Self::new(move |ctx, test| Ok(a.contains(ctx, test)? && !b.contains(ctx, test)?))
111    }
112
113    /// Construct a set which contains all tests which are contained in the
114    /// either the first or the second, but not both sets.
115    ///
116    /// This is the test set created by `a ^ b`.
117    pub fn expr_sym_diff(a: Set<T>, b: Set<T>) -> Self {
118        Self::new(move |ctx, test| Ok(a.contains(ctx, test)? ^ b.contains(ctx, test)?))
119    }
120}
121
122impl<T> TryFromValue<T> for Set<T> {
123    fn try_from_value(value: Value<T>) -> Result<Self, Error> {
124        Ok(match value {
125            Value::Set(set) => set,
126            _ => {
127                return Err(Error::TypeMismatch {
128                    expected: eco_vec![Type::Set],
129                    found: value.as_type(),
130                });
131            }
132        })
133    }
134}
135
136/// Ensure Set<T> is thread safe if T is.
137#[allow(dead_code)]
138fn assert_traits() {
139    tytanic_utils::assert::send::<Set<()>>();
140    tytanic_utils::assert::sync::<Set<()>>();
141}