tytanic_filter/
lib.rs

1//! A functional set-based DSL for filtering tests in the `tytanic` test runner.
2//! See the language [reference] and [guide] for more info.
3//!
4//! Note that this is generic over the test type because it is also used in
5//! internal projects of the author. This means that contribution is welcome,
6//! but may be rejected for various reasons not apparent in `tytanic` only.
7//!
8//! This libary is still unstable to some degree, the inner test set types are
9//! very opaque and not easily printed or inspected because of this, this may
10//! change in the future.
11//!
12//! [reference]: https://tingerrr.github.io/tytanic/reference/test-sets/index.html
13//! [guide]: https://tingerrr.github.io/tytanic/guides/test-sets.html
14
15use ecow::EcoString;
16use eval::Value;
17use thiserror::Error;
18
19use crate::eval::{Eval, Test};
20
21pub mod ast;
22pub mod eval;
23
24/// A generic test set expression filter, this filter checks whether a test
25/// should be filtered out by checking it against the inner test set within its
26/// evaluation context.
27///
28/// This also includes extra parsing logic for the special `all:` modifier
29/// prefix, which is not part of the test set grammar, but can be used by the
30/// caller to handle instances where multiple tests match but only one is
31/// usually expected.
32#[derive(Debug, Clone)]
33pub struct ExpressionFilter<T: 'static> {
34    input: EcoString,
35    all: bool,
36    ctx: eval::Context<T>,
37    set: eval::Set<T>,
38}
39
40impl<T: Test> ExpressionFilter<T> {
41    /// Parse and evaluate a string into a test set with the given context.
42    pub fn new<S: Into<EcoString>>(ctx: eval::Context<T>, input: S) -> Result<Self, Error> {
43        let input = input.into();
44
45        let (all, expr) = input
46            .strip_prefix("all:")
47            .map(|rest| (true, rest))
48            .unwrap_or((false, &input));
49
50        let set = ast::parse(expr)?.eval(&ctx).and_then(Value::expect_type)?;
51
52        Ok(Self {
53            input,
54            all,
55            ctx,
56            set,
57        })
58    }
59}
60
61impl<T> ExpressionFilter<T> {
62    /// The input expression the inner test set was parsed from.
63    pub fn input(&self) -> &str {
64        &self.input
65    }
66
67    /// Whether this test set expression has the special `all:` modifier.
68    ///
69    /// Handling this is up to the caller and has no impact on the inner test
70    /// set.
71    pub fn all(&self) -> bool {
72        self.all
73    }
74
75    /// The context used to evaluate the inner test set.
76    pub fn ctx(&self) -> &eval::Context<T> {
77        &self.ctx
78    }
79
80    /// The inner test set.
81    pub fn set(&self) -> &eval::Set<T> {
82        &self.set
83    }
84}
85
86impl<T> ExpressionFilter<T> {
87    /// Applies a function to the inner test set, this is useful for
88    /// optimization or for adding implicit test sets like wrapping a test set
89    /// in `(...) ~ skip()`.
90    pub fn map<F>(self, f: F) -> Self
91    where
92        F: FnOnce(eval::Set<T>) -> eval::Set<T>,
93    {
94        Self {
95            set: f(self.set),
96            ..self
97        }
98    }
99}
100
101impl<T> ExpressionFilter<T> {
102    /// Whether the given test is contained in this test set. Note that this
103    /// means a return value of `true` should _not_ be filtered out, but
104    /// included in the set of tests to operate on.
105    pub fn contains(&self, test: &T) -> Result<bool, eval::Error> {
106        self.set.contains(&self.ctx, test)
107    }
108}
109
110/// Returned by [`ExpressionFilter::new`].
111#[derive(Debug, Error)]
112pub enum Error {
113    /// A error occurred during parsing.
114    #[error(transparent)]
115    Parse(#[from] ast::Error),
116
117    /// An error occurred during evaluation.
118    #[error(transparent)]
119    Eval(#[from] eval::Error),
120}