Skip to main content

helix_ast/
expr.rs

1use serde::{Deserialize, Serialize};
2
3use crate::value::{PropertyInput, PropertyValue};
4/// Computed expression.
5#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
6#[serde(rename_all = "snake_case")]
7pub enum Expr {
8    /// Property reference.
9    Property(String),
10    /// Current element ID.
11    Id,
12    /// Current UTC timestamp in milliseconds.
13    Timestamp,
14    /// Current typed datetime.
15    DateTimeNow,
16    /// Literal value.
17    Constant(PropertyValue),
18    /// Runtime parameter reference.
19    Param(String),
20    /// Addition.
21    Add { left: Box<Expr>, right: Box<Expr> },
22    /// Subtraction.
23    Sub { left: Box<Expr>, right: Box<Expr> },
24    /// Multiplication.
25    Mul { left: Box<Expr>, right: Box<Expr> },
26    /// Division.
27    Div { left: Box<Expr>, right: Box<Expr> },
28    /// Modulo.
29    Mod { left: Box<Expr>, right: Box<Expr> },
30    /// Numeric negation.
31    Neg { expr: Box<Expr> },
32    /// Conditional expression.
33    Case {
34        /// Ordered predicate/expression branches.
35        when_then: Vec<WhenThen>,
36        /// Optional fallback expression.
37        #[serde(default, skip_serializing_if = "Option::is_none")]
38        else_expr: Option<Box<Expr>>,
39    },
40}
41
42/// One conditional expression branch.
43#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
44pub struct WhenThen {
45    /// Condition to test.
46    pub when: Predicate,
47    /// Expression returned when `when` matches.
48    pub then: Expr,
49}
50
51impl Expr {
52    /// Create a property reference expression.
53    pub fn prop(name: impl Into<String>) -> Self {
54        Self::Property(name.into())
55    }
56
57    /// Create a literal expression.
58    pub fn val(value: impl Into<PropertyValue>) -> Self {
59        Self::Constant(value.into())
60    }
61
62    /// Create an ID expression.
63    pub fn id() -> Self {
64        Self::Id
65    }
66
67    /// Create a timestamp expression.
68    pub fn timestamp() -> Self {
69        Self::Timestamp
70    }
71
72    /// Create a datetime expression.
73    pub fn datetime() -> Self {
74        Self::DateTimeNow
75    }
76
77    /// Create a parameter reference expression.
78    pub fn param(name: impl Into<String>) -> Self {
79        Self::Param(name.into())
80    }
81
82    /// Addition.
83    pub fn add_expr(self, other: Expr) -> Self {
84        Self::Add {
85            left: Box::new(self),
86            right: Box::new(other),
87        }
88    }
89
90    /// Subtraction.
91    pub fn sub_expr(self, other: Expr) -> Self {
92        Self::Sub {
93            left: Box::new(self),
94            right: Box::new(other),
95        }
96    }
97
98    /// Multiplication.
99    pub fn mul_expr(self, other: Expr) -> Self {
100        Self::Mul {
101            left: Box::new(self),
102            right: Box::new(other),
103        }
104    }
105
106    /// Division.
107    pub fn div_expr(self, other: Expr) -> Self {
108        Self::Div {
109            left: Box::new(self),
110            right: Box::new(other),
111        }
112    }
113
114    /// Modulo.
115    pub fn modulo(self, other: Expr) -> Self {
116        Self::Mod {
117            left: Box::new(self),
118            right: Box::new(other),
119        }
120    }
121
122    /// Negation.
123    pub fn neg_expr(self) -> Self {
124        Self::Neg {
125            expr: Box::new(self),
126        }
127    }
128
129    /// Backwards-compatible addition builder.
130    #[allow(clippy::should_implement_trait)]
131    pub fn add(self, other: Expr) -> Self {
132        self.add_expr(other)
133    }
134
135    /// Backwards-compatible subtraction builder.
136    #[allow(clippy::should_implement_trait)]
137    pub fn sub(self, other: Expr) -> Self {
138        self.sub_expr(other)
139    }
140
141    /// Backwards-compatible multiplication builder.
142    #[allow(clippy::should_implement_trait)]
143    pub fn mul(self, other: Expr) -> Self {
144        self.mul_expr(other)
145    }
146
147    /// Backwards-compatible division builder.
148    #[allow(clippy::should_implement_trait)]
149    pub fn div(self, other: Expr) -> Self {
150        self.div_expr(other)
151    }
152
153    /// Backwards-compatible negation builder.
154    #[allow(clippy::should_implement_trait)]
155    pub fn neg(self) -> Self {
156        self.neg_expr()
157    }
158
159    /// Create a conditional expression.
160    pub fn case(when_then: Vec<(Predicate, Expr)>, else_expr: Option<Expr>) -> Self {
161        Self::Case {
162            when_then: when_then
163                .into_iter()
164                .map(|(when, then)| WhenThen { when, then })
165                .collect(),
166            else_expr: else_expr.map(Box::new),
167        }
168    }
169}
170/// A non-negative stream bound.
171#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
172#[serde(rename_all = "snake_case")]
173pub enum StreamBound {
174    /// Literal bound.
175    Literal(usize),
176    /// Runtime expression bound.
177    Expr(Expr),
178}
179
180impl StreamBound {
181    /// Create a literal bound.
182    pub fn literal(value: usize) -> Self {
183        Self::Literal(value)
184    }
185
186    /// Create an expression bound.
187    pub fn expr(expr: Expr) -> Self {
188        Self::Expr(expr)
189    }
190}
191
192impl From<usize> for StreamBound {
193    fn from(value: usize) -> Self {
194        Self::Literal(value)
195    }
196}
197
198impl From<u32> for StreamBound {
199    fn from(value: u32) -> Self {
200        Self::Literal(value as usize)
201    }
202}
203
204impl From<u16> for StreamBound {
205    fn from(value: u16) -> Self {
206        Self::Literal(value as usize)
207    }
208}
209
210impl From<u8> for StreamBound {
211    fn from(value: u8) -> Self {
212        Self::Literal(value as usize)
213    }
214}
215
216impl From<i64> for StreamBound {
217    fn from(value: i64) -> Self {
218        if value >= 0 {
219            Self::Literal(value as usize)
220        } else {
221            Self::Expr(Expr::val(value))
222        }
223    }
224}
225
226impl From<i32> for StreamBound {
227    fn from(value: i32) -> Self {
228        if value >= 0 {
229            Self::Literal(value as usize)
230        } else {
231            Self::Expr(Expr::val(value))
232        }
233    }
234}
235
236impl From<Expr> for StreamBound {
237    fn from(value: Expr) -> Self {
238        Self::Expr(value)
239    }
240}
241
242/// Comparison operator.
243#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
244#[serde(rename_all = "snake_case")]
245pub enum CompareOp {
246    /// Equal.
247    Eq,
248    /// Not equal.
249    Neq,
250    /// Greater than.
251    Gt,
252    /// Greater than or equal.
253    Gte,
254    /// Less than.
255    Lt,
256    /// Less than or equal.
257    Lte,
258}
259
260/// Predicate expression.
261#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
262#[serde(rename_all = "snake_case")]
263pub enum Predicate {
264    /// Equality comparison.
265    Eq { left: Expr, right: Expr },
266    /// Inequality comparison.
267    Neq { left: Expr, right: Expr },
268    /// Greater-than comparison.
269    Gt { left: Expr, right: Expr },
270    /// Greater-than-or-equal comparison.
271    Gte { left: Expr, right: Expr },
272    /// Less-than comparison.
273    Lt { left: Expr, right: Expr },
274    /// Less-than-or-equal comparison.
275    Lte { left: Expr, right: Expr },
276    /// Inclusive range comparison.
277    Between { value: Expr, min: Expr, max: Expr },
278    /// Property exists.
279    HasKey { property: String },
280    /// Property is null or missing.
281    IsNull { property: String },
282    /// Property exists and is not null.
283    IsNotNull { property: String },
284    /// String starts with prefix.
285    StartsWith { value: Expr, prefix: Expr },
286    /// String ends with suffix.
287    EndsWith { value: Expr, suffix: Expr },
288    /// String contains substring.
289    Contains { value: Expr, substring: Expr },
290    /// Value is in a list.
291    IsIn { value: Expr, values: Expr },
292    /// Logical AND.
293    And { predicates: Vec<Predicate> },
294    /// Logical OR.
295    Or { predicates: Vec<Predicate> },
296    /// Logical NOT.
297    Not { predicate: Box<Predicate> },
298    /// Explicit expression comparison.
299    Compare {
300        /// Left expression.
301        left: Expr,
302        /// Operator.
303        op: CompareOp,
304        /// Right expression.
305        right: Expr,
306    },
307}
308
309/// Source predicates are intentionally the same AST shape as normal predicates.
310pub type SourcePredicate = Predicate;
311
312impl Predicate {
313    /// Create an equality predicate.
314    pub fn eq(property: impl Into<String>, value: impl Into<PropertyInput>) -> Self {
315        Self::Eq {
316            left: Expr::prop(property),
317            right: value.into().into_expr(),
318        }
319    }
320
321    /// Create a not-equals predicate.
322    pub fn neq(property: impl Into<String>, value: impl Into<PropertyInput>) -> Self {
323        Self::Neq {
324            left: Expr::prop(property),
325            right: value.into().into_expr(),
326        }
327    }
328
329    /// Create a greater-than predicate.
330    pub fn gt(property: impl Into<String>, value: impl Into<PropertyInput>) -> Self {
331        Self::Gt {
332            left: Expr::prop(property),
333            right: value.into().into_expr(),
334        }
335    }
336
337    /// Create a greater-than-or-equal predicate.
338    pub fn gte(property: impl Into<String>, value: impl Into<PropertyInput>) -> Self {
339        Self::Gte {
340            left: Expr::prop(property),
341            right: value.into().into_expr(),
342        }
343    }
344
345    /// Create a less-than predicate.
346    pub fn lt(property: impl Into<String>, value: impl Into<PropertyInput>) -> Self {
347        Self::Lt {
348            left: Expr::prop(property),
349            right: value.into().into_expr(),
350        }
351    }
352
353    /// Create a less-than-or-equal predicate.
354    pub fn lte(property: impl Into<String>, value: impl Into<PropertyInput>) -> Self {
355        Self::Lte {
356            left: Expr::prop(property),
357            right: value.into().into_expr(),
358        }
359    }
360
361    /// Create a between predicate.
362    pub fn between(
363        property: impl Into<String>,
364        min: impl Into<PropertyInput>,
365        max: impl Into<PropertyInput>,
366    ) -> Self {
367        Self::Between {
368            value: Expr::prop(property),
369            min: min.into().into_expr(),
370            max: max.into().into_expr(),
371        }
372    }
373
374    /// Create a has-key predicate.
375    pub fn has_key(property: impl Into<String>) -> Self {
376        Self::HasKey {
377            property: property.into(),
378        }
379    }
380
381    /// Create an is-null predicate.
382    pub fn is_null(property: impl Into<String>) -> Self {
383        Self::IsNull {
384            property: property.into(),
385        }
386    }
387
388    /// Create an is-not-null predicate.
389    pub fn is_not_null(property: impl Into<String>) -> Self {
390        Self::IsNotNull {
391            property: property.into(),
392        }
393    }
394
395    /// Create a starts-with predicate.
396    pub fn starts_with(property: impl Into<String>, prefix: impl Into<String>) -> Self {
397        Self::StartsWith {
398            value: Expr::prop(property),
399            prefix: Expr::val(prefix.into()),
400        }
401    }
402
403    /// Create an ends-with predicate.
404    pub fn ends_with(property: impl Into<String>, suffix: impl Into<String>) -> Self {
405        Self::EndsWith {
406            value: Expr::prop(property),
407            suffix: Expr::val(suffix.into()),
408        }
409    }
410
411    /// Create a contains predicate.
412    pub fn contains(property: impl Into<String>, substring: impl Into<String>) -> Self {
413        Self::Contains {
414            value: Expr::prop(property),
415            substring: Expr::val(substring.into()),
416        }
417    }
418
419    /// Create a parameterized contains predicate.
420    pub fn contains_param(property: impl Into<String>, param_name: impl Into<String>) -> Self {
421        Self::Contains {
422            value: Expr::prop(property),
423            substring: Expr::param(param_name),
424        }
425    }
426
427    /// Create an IN predicate.
428    pub fn is_in(property: impl Into<String>, values: impl Into<PropertyValue>) -> Self {
429        Self::IsIn {
430            value: Expr::prop(property),
431            values: Expr::val(values.into()),
432        }
433    }
434
435    /// Create an IN predicate from an expression.
436    pub fn is_in_expr(property: impl Into<String>, values: Expr) -> Self {
437        Self::IsIn {
438            value: Expr::prop(property),
439            values,
440        }
441    }
442
443    /// Create a parameterized IN predicate.
444    pub fn is_in_param(property: impl Into<String>, param_name: impl Into<String>) -> Self {
445        Self::is_in_expr(property, Expr::param(param_name))
446    }
447
448    /// Combine predicates with AND.
449    pub fn and(predicates: Vec<Predicate>) -> Self {
450        Self::And { predicates }
451    }
452
453    /// Combine predicates with OR.
454    pub fn or(predicates: Vec<Predicate>) -> Self {
455        Self::Or { predicates }
456    }
457
458    /// Negate a predicate.
459    #[allow(clippy::should_implement_trait)]
460    pub fn not(predicate: Predicate) -> Self {
461        Self::Not {
462            predicate: Box::new(predicate),
463        }
464    }
465
466    /// Create an expression comparison predicate.
467    pub fn compare(left: Expr, op: CompareOp, right: Expr) -> Self {
468        Self::Compare { left, op, right }
469    }
470
471    /// Create a parameterized equality predicate.
472    pub fn eq_param(property: impl Into<String>, param_name: impl Into<String>) -> Self {
473        Self::eq(property, Expr::param(param_name))
474    }
475
476    /// Create a parameterized not-equals predicate.
477    pub fn neq_param(property: impl Into<String>, param_name: impl Into<String>) -> Self {
478        Self::neq(property, Expr::param(param_name))
479    }
480
481    /// Create a parameterized greater-than predicate.
482    pub fn gt_param(property: impl Into<String>, param_name: impl Into<String>) -> Self {
483        Self::gt(property, Expr::param(param_name))
484    }
485
486    /// Create a parameterized greater-than-or-equal predicate.
487    pub fn gte_param(property: impl Into<String>, param_name: impl Into<String>) -> Self {
488        Self::gte(property, Expr::param(param_name))
489    }
490
491    /// Create a parameterized less-than predicate.
492    pub fn lt_param(property: impl Into<String>, param_name: impl Into<String>) -> Self {
493        Self::lt(property, Expr::param(param_name))
494    }
495
496    /// Create a parameterized less-than-or-equal predicate.
497    pub fn lte_param(property: impl Into<String>, param_name: impl Into<String>) -> Self {
498        Self::lte(property, Expr::param(param_name))
499    }
500}