Skip to main content

github_actions_expressions/
op.rs

1//! Unary and binary expressions and operators.
2
3use crate::SpannedExpr;
4
5/// Binary operations allowed in an expression.
6#[derive(Debug, PartialEq)]
7pub enum BinOp {
8    /// `expr && expr`
9    And,
10    /// `expr || expr`
11    Or,
12    /// `expr == expr`
13    Eq,
14    /// `expr != expr`
15    Neq,
16    /// `expr > expr`
17    Gt,
18    /// `expr >= expr`
19    Ge,
20    /// `expr < expr`
21    Lt,
22    /// `expr <= expr`
23    Le,
24}
25
26/// Represents a binary expression.
27///
28/// Binary expressions can be either logical or arithmetic.
29#[derive(Debug, PartialEq)]
30pub struct BinExpr<'src> {
31    /// The LHS of the expr.
32    pub lhs: Box<SpannedExpr<'src>>,
33    /// The binary operator.
34    pub op: BinOp,
35    /// The RHS of the expr.
36    pub rhs: Box<SpannedExpr<'src>>,
37}
38
39/// Unary operations allowed in an expression.
40#[derive(Debug, PartialEq)]
41pub enum UnOp {
42    /// `!expr`
43    Not,
44}
45
46#[cfg(test)]
47mod tests {
48    use crate::{Error, Expr};
49
50    #[test]
51    fn test_evaluate_constant_binary_operations() -> Result<(), Error> {
52        use crate::Evaluation;
53
54        let test_cases = &[
55            // Boolean operations
56            ("true && true", Evaluation::Boolean(true)),
57            ("true && false", Evaluation::Boolean(false)),
58            ("false && true", Evaluation::Boolean(false)),
59            ("false && false", Evaluation::Boolean(false)),
60            ("true || true", Evaluation::Boolean(true)),
61            ("true || false", Evaluation::Boolean(true)),
62            ("false || true", Evaluation::Boolean(true)),
63            ("false || false", Evaluation::Boolean(false)),
64            // Equality operations
65            ("1 == 1", Evaluation::Boolean(true)),
66            ("1 == 2", Evaluation::Boolean(false)),
67            ("'hello' == 'hello'", Evaluation::Boolean(true)),
68            ("'hello' == 'world'", Evaluation::Boolean(false)),
69            ("true == true", Evaluation::Boolean(true)),
70            ("true == false", Evaluation::Boolean(false)),
71            ("1 != 2", Evaluation::Boolean(true)),
72            ("1 != 1", Evaluation::Boolean(false)),
73            // Comparison operations
74            ("1 < 2", Evaluation::Boolean(true)),
75            ("2 < 1", Evaluation::Boolean(false)),
76            ("1 <= 1", Evaluation::Boolean(true)),
77            ("1 <= 2", Evaluation::Boolean(true)),
78            ("2 <= 1", Evaluation::Boolean(false)),
79            ("2 > 1", Evaluation::Boolean(true)),
80            ("1 > 2", Evaluation::Boolean(false)),
81            ("1 >= 1", Evaluation::Boolean(true)),
82            ("2 >= 1", Evaluation::Boolean(true)),
83            ("1 >= 2", Evaluation::Boolean(false)),
84        ];
85
86        for (expr_str, expected) in test_cases {
87            let expr = Expr::parse(expr_str)?;
88            let result = expr.consteval().unwrap();
89            assert_eq!(result, *expected, "Failed for expression: {}", expr_str);
90        }
91
92        Ok(())
93    }
94
95    #[test]
96    fn test_cross_type_comparison_with_whitespace() -> Result<(), Error> {
97        use crate::Evaluation;
98
99        let test_cases = &[
100            // Strings with whitespace should be trimmed before number coercion.
101            ("'   1   ' == 1", Evaluation::Boolean(true)),
102            ("' 42 ' == 42", Evaluation::Boolean(true)),
103            ("'  0  ' == 0", Evaluation::Boolean(true)),
104            ("'  3.14  ' == 3.14", Evaluation::Boolean(true)),
105            ("'   1   ' != 1", Evaluation::Boolean(false)),
106            ("' 2 ' < 3", Evaluation::Boolean(true)),
107            ("' 5 ' > 3", Evaluation::Boolean(true)),
108        ];
109
110        for (expr_str, expected) in test_cases {
111            let expr = Expr::parse(expr_str)?;
112            let result = expr.consteval().unwrap();
113            assert_eq!(result, *expected, "Failed for expression: {}", expr_str);
114        }
115
116        Ok(())
117    }
118}