Skip to main content

elo_rust/ast/
mod.rs

1//! AST (Abstract Syntax Tree) module for ELO expressions
2//!
3//! Defines the complete set of ELO expression types matching the language specification.
4//! Each expression variant represents a different construct in the ELO language.
5
6use std::fmt;
7
8pub mod visitor;
9
10pub use visitor::Visitor;
11
12/// Top-level ELO expression type
13///
14/// Represents any valid ELO expression that can be parsed and executed.
15/// This is an exhaustive enum of all expression forms in ELO.
16#[derive(Debug, Clone, PartialEq)]
17pub enum Expr {
18    /// Literal values: numbers (int/float) or booleans
19    Literal(Literal),
20
21    /// Null literal
22    Null,
23
24    /// Variable reference (identifier)
25    Identifier(String),
26
27    /// Field access: receiver.field (e.g., user.age)
28    FieldAccess {
29        /// The expression being accessed
30        receiver: Box<Expr>,
31        /// The field name
32        field: String,
33    },
34
35    /// Binary operation: left op right
36    BinaryOp {
37        /// The binary operator
38        op: BinaryOperator,
39        /// Left operand
40        left: Box<Expr>,
41        /// Right operand
42        right: Box<Expr>,
43    },
44
45    /// Unary operation: op operand
46    UnaryOp {
47        /// The unary operator
48        op: UnaryOperator,
49        /// The operand
50        operand: Box<Expr>,
51    },
52
53    /// Function call: name(args)
54    FunctionCall {
55        /// Function name
56        name: String,
57        /// Function arguments
58        args: Vec<Expr>,
59    },
60
61    /// Lambda expression: param ~> body
62    Lambda {
63        /// Parameter name
64        param: String,
65        /// Lambda body expression
66        body: Box<Expr>,
67    },
68
69    /// Let binding: let name = value in body
70    Let {
71        /// Variable name being bound
72        name: String,
73        /// Value expression
74        value: Box<Expr>,
75        /// Body expression (scope where binding is available)
76        body: Box<Expr>,
77    },
78
79    /// If conditional: if condition then branch_a else branch_b
80    If {
81        /// Condition expression
82        condition: Box<Expr>,
83        /// Then branch
84        then_branch: Box<Expr>,
85        /// Else branch
86        else_branch: Box<Expr>,
87    },
88
89    /// Array literal: [expr1, expr2, ...]
90    Array(Vec<Expr>),
91
92    /// Object literal: {key1: value1, key2: value2, ...}
93    Object(Vec<(String, Expr)>),
94
95    /// Pipe operator: expr |> func() |> ...
96    Pipe {
97        /// The value being piped
98        value: Box<Expr>,
99        /// Functions to pipe through (in order)
100        functions: Vec<Expr>,
101    },
102
103    /// Alternative operator: expr ?| default
104    Alternative {
105        /// Primary expression
106        primary: Box<Expr>,
107        /// Alternative/default expression
108        alternative: Box<Expr>,
109    },
110
111    /// Guard expression: guard condition in expr
112    Guard {
113        /// Condition that must be true
114        condition: Box<Expr>,
115        /// Expression to evaluate if guard passes
116        body: Box<Expr>,
117    },
118
119    /// Date literal: @date(2024-01-15)
120    Date(String), // ISO8601 date: YYYY-MM-DD
121
122    /// DateTime literal: @datetime(2024-01-15T10:30:00Z)
123    DateTime(String), // ISO8601 datetime
124
125    /// Duration literal: @duration(P1D), @duration(PT1H30M)
126    Duration(String), // ISO8601 duration
127
128    /// Temporal keyword: NOW, TODAY, TOMORROW, etc.
129    TemporalKeyword(TemporalKeyword),
130
131    /// String literal (explicitly quoted with single quotes)
132    String(String),
133}
134
135/// Literal value types
136#[derive(Debug, Clone, PartialEq)]
137pub enum Literal {
138    /// Integer literal
139    Integer(i64),
140
141    /// Float literal
142    Float(f64),
143
144    /// Boolean literal
145    Boolean(bool),
146}
147
148/// Binary operators supported in ELO
149#[derive(Debug, Clone, Copy, PartialEq, Eq)]
150pub enum BinaryOperator {
151    // Arithmetic operators
152    /// Addition: +
153    Add,
154    /// Subtraction: -
155    Sub,
156    /// Multiplication: *
157    Mul,
158    /// Division: /
159    Div,
160    /// Modulo: %
161    Mod,
162    /// Exponentiation: ^
163    Pow,
164
165    // Comparison operators
166    /// Equality: ==
167    Eq,
168    /// Inequality: !=
169    Neq,
170    /// Less than: <
171    Lt,
172    /// Less than or equal: <=
173    Lte,
174    /// Greater than: >
175    Gt,
176    /// Greater than or equal: >=
177    Gte,
178
179    // Logical operators
180    /// Logical AND: &&
181    And,
182    /// Logical OR: ||
183    Or,
184}
185
186/// Unary operators supported in ELO
187#[derive(Debug, Clone, Copy, PartialEq, Eq)]
188pub enum UnaryOperator {
189    /// Logical NOT: !
190    Not,
191    /// Negation: -
192    Neg,
193    /// Unary plus: +
194    Plus,
195}
196
197/// Temporal keywords for date/time operations
198#[derive(Debug, Clone, Copy, PartialEq, Eq)]
199pub enum TemporalKeyword {
200    /// Current date and time
201    Now,
202    /// Current date (start of day)
203    Today,
204    /// Next calendar day
205    Tomorrow,
206    /// Previous calendar day
207    Yesterday,
208
209    // Period boundaries (boundaries are inclusive start of period)
210    /// Start of current day
211    StartOfDay,
212    /// End of current day
213    EndOfDay,
214    /// Start of current week
215    StartOfWeek,
216    /// End of current week
217    EndOfWeek,
218    /// Start of current month
219    StartOfMonth,
220    /// End of current month
221    EndOfMonth,
222    /// Start of current quarter
223    StartOfQuarter,
224    /// End of current quarter
225    EndOfQuarter,
226    /// Start of current year
227    StartOfYear,
228    /// End of current year
229    EndOfYear,
230
231    // Extremes
232    /// Beginning of time (used for range checking)
233    BeginningOfTime,
234    /// End of time (used for range checking)
235    EndOfTime,
236}
237
238impl fmt::Display for BinaryOperator {
239    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
240        match self {
241            Self::Add => write!(f, "+"),
242            Self::Sub => write!(f, "-"),
243            Self::Mul => write!(f, "*"),
244            Self::Div => write!(f, "/"),
245            Self::Mod => write!(f, "%"),
246            Self::Pow => write!(f, "^"),
247            Self::Eq => write!(f, "=="),
248            Self::Neq => write!(f, "!="),
249            Self::Lt => write!(f, "<"),
250            Self::Lte => write!(f, "<="),
251            Self::Gt => write!(f, ">"),
252            Self::Gte => write!(f, ">="),
253            Self::And => write!(f, "&&"),
254            Self::Or => write!(f, "||"),
255        }
256    }
257}
258
259impl fmt::Display for UnaryOperator {
260    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
261        match self {
262            Self::Not => write!(f, "!"),
263            Self::Neg => write!(f, "-"),
264            Self::Plus => write!(f, "+"),
265        }
266    }
267}
268
269#[cfg(test)]
270mod tests {
271    use super::*;
272
273    #[test]
274    fn test_literal_integer() {
275        let expr = Expr::Literal(Literal::Integer(42));
276        assert_eq!(expr, Expr::Literal(Literal::Integer(42)));
277    }
278
279    #[test]
280    fn test_literal_float() {
281        let expr = Expr::Literal(Literal::Float(3.15));
282        assert_eq!(expr, Expr::Literal(Literal::Float(3.15)));
283    }
284
285    #[test]
286    fn test_literal_boolean() {
287        let expr = Expr::Literal(Literal::Boolean(true));
288        assert_eq!(expr, Expr::Literal(Literal::Boolean(true)));
289    }
290
291    #[test]
292    fn test_identifier() {
293        let expr = Expr::Identifier("age".to_string());
294        assert_eq!(expr, Expr::Identifier("age".to_string()));
295    }
296
297    #[test]
298    fn test_string_literal() {
299        let expr = Expr::String("hello".to_string());
300        assert_eq!(expr, Expr::String("hello".to_string()));
301    }
302
303    #[test]
304    fn test_field_access() {
305        let expr = Expr::FieldAccess {
306            receiver: Box::new(Expr::Identifier("user".to_string())),
307            field: "age".to_string(),
308        };
309        matches!(expr, Expr::FieldAccess { .. });
310    }
311
312    #[test]
313    fn test_binary_op() {
314        let expr = Expr::BinaryOp {
315            op: BinaryOperator::Add,
316            left: Box::new(Expr::Literal(Literal::Integer(1))),
317            right: Box::new(Expr::Literal(Literal::Integer(2))),
318        };
319        matches!(expr, Expr::BinaryOp { .. });
320    }
321
322    #[test]
323    fn test_unary_op() {
324        let expr = Expr::UnaryOp {
325            op: UnaryOperator::Not,
326            operand: Box::new(Expr::Literal(Literal::Boolean(true))),
327        };
328        matches!(expr, Expr::UnaryOp { .. });
329    }
330
331    #[test]
332    fn test_function_call() {
333        let expr = Expr::FunctionCall {
334            name: "length".to_string(),
335            args: vec![Expr::Identifier("name".to_string())],
336        };
337        matches!(expr, Expr::FunctionCall { .. });
338    }
339
340    #[test]
341    fn test_lambda() {
342        let expr = Expr::Lambda {
343            param: "x".to_string(),
344            body: Box::new(Expr::BinaryOp {
345                op: BinaryOperator::Mul,
346                left: Box::new(Expr::Identifier("x".to_string())),
347                right: Box::new(Expr::Literal(Literal::Integer(2))),
348            }),
349        };
350        matches!(expr, Expr::Lambda { .. });
351    }
352
353    #[test]
354    fn test_let_expr() {
355        let expr = Expr::Let {
356            name: "x".to_string(),
357            value: Box::new(Expr::Literal(Literal::Integer(1))),
358            body: Box::new(Expr::Identifier("x".to_string())),
359        };
360        matches!(expr, Expr::Let { .. });
361    }
362
363    #[test]
364    fn test_if_expr() {
365        let expr = Expr::If {
366            condition: Box::new(Expr::Literal(Literal::Boolean(true))),
367            then_branch: Box::new(Expr::Literal(Literal::Integer(1))),
368            else_branch: Box::new(Expr::Literal(Literal::Integer(2))),
369        };
370        matches!(expr, Expr::If { .. });
371    }
372
373    #[test]
374    fn test_array() {
375        let expr = Expr::Array(vec![
376            Expr::Literal(Literal::Integer(1)),
377            Expr::Literal(Literal::Integer(2)),
378        ]);
379        matches!(expr, Expr::Array(_));
380    }
381
382    #[test]
383    fn test_object() {
384        let expr = Expr::Object(vec![(
385            "key".to_string(),
386            Expr::Literal(Literal::Integer(1)),
387        )]);
388        matches!(expr, Expr::Object(_));
389    }
390
391    #[test]
392    fn test_null() {
393        let expr = Expr::Null;
394        assert_eq!(expr, Expr::Null);
395    }
396
397    #[test]
398    fn test_date_literal() {
399        let expr = Expr::Date("2024-01-15".to_string());
400        assert_eq!(expr, Expr::Date("2024-01-15".to_string()));
401    }
402
403    #[test]
404    fn test_datetime_literal() {
405        let expr = Expr::DateTime("2024-01-15T10:30:00Z".to_string());
406        assert_eq!(expr, Expr::DateTime("2024-01-15T10:30:00Z".to_string()));
407    }
408
409    #[test]
410    fn test_duration_literal() {
411        let expr = Expr::Duration("P1D".to_string());
412        assert_eq!(expr, Expr::Duration("P1D".to_string()));
413    }
414
415    #[test]
416    fn test_temporal_keyword() {
417        let expr = Expr::TemporalKeyword(TemporalKeyword::Now);
418        assert_eq!(expr, Expr::TemporalKeyword(TemporalKeyword::Now));
419    }
420
421    #[test]
422    fn test_binary_operator_display() {
423        assert_eq!(BinaryOperator::Add.to_string(), "+");
424        assert_eq!(BinaryOperator::Eq.to_string(), "==");
425        assert_eq!(BinaryOperator::And.to_string(), "&&");
426    }
427
428    #[test]
429    fn test_unary_operator_display() {
430        assert_eq!(UnaryOperator::Not.to_string(), "!");
431        assert_eq!(UnaryOperator::Neg.to_string(), "-");
432    }
433
434    #[test]
435    fn test_pipe() {
436        let expr = Expr::Pipe {
437            value: Box::new(Expr::Identifier("x".to_string())),
438            functions: vec![Expr::FunctionCall {
439                name: "double".to_string(),
440                args: vec![],
441            }],
442        };
443        matches!(expr, Expr::Pipe { .. });
444    }
445
446    #[test]
447    fn test_guard() {
448        let expr = Expr::Guard {
449            condition: Box::new(Expr::BinaryOp {
450                op: BinaryOperator::Gt,
451                left: Box::new(Expr::Identifier("x".to_string())),
452                right: Box::new(Expr::Literal(Literal::Integer(0))),
453            }),
454            body: Box::new(Expr::Identifier("x".to_string())),
455        };
456        matches!(expr, Expr::Guard { .. });
457    }
458
459    #[test]
460    fn test_alternative() {
461        let expr = Expr::Alternative {
462            primary: Box::new(Expr::Identifier("maybe".to_string())),
463            alternative: Box::new(Expr::Literal(Literal::Integer(0))),
464        };
465        matches!(expr, Expr::Alternative { .. });
466    }
467}