tytanic_filter/eval/
set.rs

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