Skip to main content

ravenrs_criteria/
ast.rs

1//! The criteria tree. It is public on purpose: programs build, inspect and
2//! rewrite criteria (security rules, saved filters), and printing a tree gives
3//! text that parses back to the same tree.
4
5use std::fmt;
6
7use crate::functions::{Aggregate, Function};
8use crate::value::Value;
9
10/// A parsed expression or filter.
11///
12/// ```
13/// use ravenrs_criteria::Criteria;
14///
15/// let c = Criteria::parse("[Price] * [Qty] > 100 And [Active]").expect("valid");
16/// assert_eq!(c.to_string(), "[Price] * [Qty] > 100 And [Active]");
17/// assert_eq!(c.properties(), ["Price", "Qty", "Active"]);
18/// ```
19#[derive(Clone, Debug, PartialEq)]
20#[non_exhaustive]
21pub enum Criteria {
22    /// A literal.
23    Value(Value),
24    /// A field path, such as `Customer.Name`; a leading `^` reads the parent row.
25    Property(Vec<String>),
26    /// A parameter, `?Name`.
27    Parameter(String),
28    /// A unary operator.
29    Unary(UnaryOp, Box<Criteria>),
30    /// A binary operator.
31    Binary(BinaryOp, Box<Criteria>, Box<Criteria>),
32    /// `And` or `Or` over two or more operands.
33    Group(GroupOp, Vec<Criteria>),
34    /// `value In (a, b, c)`.
35    In(Box<Criteria>, Vec<Criteria>),
36    /// `value Between(low, high)`.
37    Between(Box<Criteria>, Box<Criteria>, Box<Criteria>),
38    /// `value Is Null`.
39    IsNull(Box<Criteria>),
40    /// A built-in function.
41    Function(Function, Vec<Criteria>),
42    /// A function resolved by name when evaluated (see [`crate::Environment::register`]).
43    Custom(String, Vec<Criteria>),
44    /// An aggregate over the current scope, or over a nested collection.
45    Aggregate(Box<AggregateCall>),
46}
47
48/// An aggregate call.
49#[derive(Clone, Debug, PartialEq)]
50pub struct AggregateCall {
51    /// The nested collection, or `None` for the rows in scope.
52    pub collection: Option<Vec<String>>,
53    /// `[Items][[Qty] > 0]`: only rows where this holds.
54    pub condition: Option<Criteria>,
55    /// The function.
56    pub function: Aggregate,
57    /// Its arguments, evaluated per row.
58    pub args: Vec<Criteria>,
59}
60
61/// Unary operators.
62#[derive(Clone, Copy, Debug, PartialEq, Eq)]
63#[non_exhaustive]
64pub enum UnaryOp {
65    /// Logical `Not`.
66    Not,
67    /// Arithmetic `-`.
68    Minus,
69    /// Bitwise `~`.
70    BitNot,
71}
72
73/// Binary operators.
74#[derive(Clone, Copy, Debug, PartialEq, Eq)]
75#[non_exhaustive]
76#[allow(missing_docs, reason = "the operators speak for themselves")]
77pub enum BinaryOp {
78    Add,
79    Sub,
80    Mul,
81    Div,
82    Rem,
83    Eq,
84    Ne,
85    Lt,
86    Le,
87    Gt,
88    Ge,
89    /// `Like`, with `%` and `_` wildcards.
90    Like,
91    BitAnd,
92    BitOr,
93    BitXor,
94}
95
96/// `And` or `Or`.
97#[derive(Clone, Copy, Debug, PartialEq, Eq)]
98#[non_exhaustive]
99pub enum GroupOp {
100    /// All operands hold.
101    And,
102    /// Any operand holds.
103    Or,
104}
105
106impl BinaryOp {
107    fn symbol(self) -> &'static str {
108        match self {
109            Self::Add => "+",
110            Self::Sub => "-",
111            Self::Mul => "*",
112            Self::Div => "/",
113            Self::Rem => "%",
114            Self::Eq => "=",
115            Self::Ne => "<>",
116            Self::Lt => "<",
117            Self::Le => "<=",
118            Self::Gt => ">",
119            Self::Ge => ">=",
120            Self::Like => "Like",
121            Self::BitAnd => "&",
122            Self::BitOr => "|",
123            Self::BitXor => "^",
124        }
125    }
126
127    fn precedence(self) -> u8 {
128        match self {
129            Self::BitOr => 4,
130            Self::BitXor => 5,
131            Self::BitAnd => 6,
132            Self::Eq | Self::Ne | Self::Lt | Self::Le | Self::Gt | Self::Ge | Self::Like => 7,
133            Self::Add | Self::Sub => 8,
134            Self::Mul | Self::Div | Self::Rem => 9,
135        }
136    }
137}
138
139impl std::ops::Not for Criteria {
140    type Output = Self;
141
142    /// `Not self`.
143    fn not(self) -> Self {
144        Self::Unary(UnaryOp::Not, Box::new(self))
145    }
146}
147
148impl Criteria {
149    /// Parses `source`; see the crate documentation for the syntax.
150    pub fn parse(source: &str) -> Result<Self, crate::ParseError> {
151        crate::parse::parse(source)
152    }
153
154    /// Parses assignments, `field = expression; ...`, as an update sets fields: each field with
155    /// the expression that gives its new value.
156    ///
157    /// ```
158    /// use ravenrs_criteria::Criteria;
159    ///
160    /// let set = Criteria::assignments("state = 'done'; [finished] = Today()").unwrap();
161    /// assert_eq!(set[0].0, "state");
162    /// assert_eq!(set[1].0, "finished");
163    /// ```
164    pub fn assignments(source: &str) -> Result<Vec<(String, Self)>, crate::ParseError> {
165        crate::parse::parse_assignments(source)
166    }
167
168    /// A field.
169    pub fn property(path: &str) -> Self {
170        Self::Property(path.split('.').map(str::to_owned).collect())
171    }
172
173    /// A literal.
174    pub fn value(value: impl Into<Value>) -> Self {
175        Self::Value(value.into())
176    }
177
178    /// `self = other`.
179    pub fn equals(self, other: Self) -> Self {
180        Self::Binary(BinaryOp::Eq, Box::new(self), Box::new(other))
181    }
182
183    /// `self And other`, flattening nested `And`s.
184    pub fn and(self, other: Self) -> Self {
185        Self::group(GroupOp::And, self, other)
186    }
187
188    /// `self Or other`, flattening nested `Or`s.
189    pub fn or(self, other: Self) -> Self {
190        Self::group(GroupOp::Or, self, other)
191    }
192
193    fn group(op: GroupOp, a: Self, b: Self) -> Self {
194        let mut items = Vec::new();
195        for c in [a, b] {
196            match c {
197                Self::Group(o, inner) if o == op => items.extend(inner),
198                other => items.push(other),
199            }
200        }
201        Self::Group(op, items)
202    }
203
204    /// Calls `f` on this node and everything under it.
205    pub fn walk(&self, f: &mut dyn FnMut(&Criteria)) {
206        f(self);
207        match self {
208            Self::Value(_) | Self::Property(_) | Self::Parameter(_) => {}
209            Self::Unary(_, a) | Self::IsNull(a) => a.walk(f),
210            Self::Binary(_, a, b) => {
211                a.walk(f);
212                b.walk(f);
213            }
214            Self::Group(_, items) | Self::Function(_, items) | Self::Custom(_, items) => {
215                items.iter().for_each(|c| c.walk(f));
216            }
217            Self::In(a, items) => {
218                a.walk(f);
219                items.iter().for_each(|c| c.walk(f));
220            }
221            Self::Between(a, b, c) => {
222                a.walk(f);
223                b.walk(f);
224                c.walk(f);
225            }
226            Self::Aggregate(call) => {
227                if let Some(c) = &call.condition {
228                    c.walk(f);
229                }
230                call.args.iter().for_each(|c| c.walk(f));
231            }
232        }
233    }
234
235    /// The field paths used, in order, without repeats.
236    pub fn properties(&self) -> Vec<String> {
237        let mut out = Vec::new();
238        self.walk(&mut |c| {
239            if let Self::Property(p) = c {
240                let path = p.join(".");
241                if !out.contains(&path) {
242                    out.push(path);
243                }
244            }
245        });
246        out
247    }
248
249    /// The parameter names used, in order, without repeats.
250    pub fn parameters(&self) -> Vec<String> {
251        let mut out = Vec::new();
252        self.walk(&mut |c| {
253            if let Self::Parameter(p) = c
254                && !out.contains(p)
255            {
256                out.push(p.clone());
257            }
258        });
259        out
260    }
261
262    /// True when any node satisfies `test`.
263    pub fn any(&self, test: &dyn Fn(&Criteria) -> bool) -> bool {
264        let mut found = false;
265        self.walk(&mut |c| found |= test(c));
266        found
267    }
268
269    /// Binding strength when printed: higher binds tighter.
270    fn precedence(&self) -> u8 {
271        match self {
272            Self::Group(GroupOp::Or, _) => 1,
273            Self::Group(GroupOp::And, _) => 2,
274            Self::Unary(UnaryOp::Not, _) => 3,
275            Self::Binary(op, ..) => op.precedence(),
276            Self::In(..) | Self::Between(..) | Self::IsNull(_) => 7,
277            Self::Unary(..) => 10,
278            _ => 11,
279        }
280    }
281
282    fn write_child(&self, f: &mut fmt::Formatter<'_>, min: u8) -> fmt::Result {
283        if self.precedence() < min { write!(f, "({self})") } else { write!(f, "{self}") }
284    }
285}
286
287fn write_list(f: &mut fmt::Formatter<'_>, items: &[Criteria]) -> fmt::Result {
288    for (i, c) in items.iter().enumerate() {
289        if i > 0 {
290            f.write_str(", ")?;
291        }
292        write!(f, "{c}")?;
293    }
294    Ok(())
295}
296
297impl fmt::Display for Criteria {
298    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
299        match self {
300            Self::Value(v) => match v {
301                Value::Null => f.write_str("Null"),
302                Value::Bool(true) => f.write_str("True"),
303                Value::Bool(false) => f.write_str("False"),
304                Value::Number(n) if *n < 0.0 => write!(f, "({})", crate::value::format_plain(*n)),
305                Value::Number(n) => f.write_str(&crate::value::format_plain(*n)),
306                Value::Text(s) => write!(f, "'{}'", s.replace('\'', "''")),
307                Value::Date(d) => write!(f, "#{d}#"),
308            },
309            Self::Property(p) => write!(f, "[{}]", p.join(".")),
310            Self::Parameter(p) => write!(f, "?{p}"),
311            Self::Unary(UnaryOp::Not, a) => {
312                f.write_str("Not ")?;
313                a.write_child(f, 3)
314            }
315            Self::Unary(UnaryOp::Minus, a) => {
316                f.write_str("-")?;
317                a.write_child(f, 10)
318            }
319            Self::Unary(UnaryOp::BitNot, a) => {
320                f.write_str("~")?;
321                a.write_child(f, 10)
322            }
323            Self::Binary(op, a, b) => {
324                let p = op.precedence();
325                a.write_child(f, p)?;
326                write!(f, " {} ", op.symbol())?;
327                // Operators associate left, so a right operand of equal strength needs parentheses.
328                b.write_child(f, p + 1)
329            }
330            Self::Group(op, items) => {
331                let (word, p) = if *op == GroupOp::And { (" And ", 3) } else { (" Or ", 2) };
332                for (i, c) in items.iter().enumerate() {
333                    if i > 0 {
334                        f.write_str(word)?;
335                    }
336                    c.write_child(f, p)?;
337                }
338                Ok(())
339            }
340            Self::In(a, items) => {
341                a.write_child(f, 8)?;
342                f.write_str(" In (")?;
343                write_list(f, items)?;
344                f.write_str(")")
345            }
346            Self::Between(a, lo, hi) => {
347                a.write_child(f, 8)?;
348                write!(f, " Between({lo}, {hi})")
349            }
350            Self::IsNull(a) => {
351                a.write_child(f, 8)?;
352                f.write_str(" Is Null")
353            }
354            Self::Function(func, args) => {
355                write!(f, "{}(", func.name())?;
356                write_list(f, args)?;
357                f.write_str(")")
358            }
359            Self::Custom(name, args) => {
360                write!(f, "{name}(")?;
361                write_list(f, args)?;
362                f.write_str(")")
363            }
364            Self::Aggregate(call) => {
365                if let Some(path) = &call.collection {
366                    write!(f, "[{}]", path.join("."))?;
367                    match &call.condition {
368                        Some(c) => write!(f, "[{c}].")?,
369                        None => f.write_str("[].")?,
370                    }
371                }
372                write!(f, "{}(", call.function.name())?;
373                write_list(f, &call.args)?;
374                f.write_str(")")
375            }
376        }
377    }
378}