hcl/expr/
operation.rs

1use super::Expression;
2use serde::Deserialize;
3
4// Re-exported for convenience.
5#[doc(inline)]
6pub use hcl_primitives::expr::{BinaryOperator, UnaryOperator};
7
8/// Operations apply a particular operator to either one or two expression terms.
9#[derive(Deserialize, Debug, PartialEq, Eq, Clone)]
10pub enum Operation {
11    /// Represents an operation that applies an operator to a single expression.
12    Unary(UnaryOp),
13    /// Represents an operation that applies an operator to two expressions.
14    Binary(BinaryOp),
15}
16
17impl From<UnaryOp> for Operation {
18    fn from(op: UnaryOp) -> Self {
19        Operation::Unary(op)
20    }
21}
22
23impl From<BinaryOp> for Operation {
24    fn from(op: BinaryOp) -> Self {
25        Operation::Binary(op)
26    }
27}
28
29/// An operation that applies an operator to one expression.
30#[derive(Deserialize, Debug, PartialEq, Eq, Clone)]
31pub struct UnaryOp {
32    /// The unary operator to use on the expression.
33    pub operator: UnaryOperator,
34    /// An expression that supports evaluation with the unary operator.
35    pub expr: Expression,
36}
37
38impl UnaryOp {
39    /// Creates a new `UnaryOp` from an operator and an expression.
40    pub fn new<T>(operator: UnaryOperator, expr: T) -> UnaryOp
41    where
42        T: Into<Expression>,
43    {
44        UnaryOp {
45            operator,
46            expr: expr.into(),
47        }
48    }
49}
50
51/// An operation that applies an operator to two expressions.
52#[derive(Deserialize, Debug, PartialEq, Eq, Clone)]
53pub struct BinaryOp {
54    /// The expression on the left-hand-side of the operation.
55    pub lhs_expr: Expression,
56    /// The binary operator to use on the expressions.
57    pub operator: BinaryOperator,
58    /// The expression on the right-hand-side of the operation.
59    pub rhs_expr: Expression,
60}
61
62impl BinaryOp {
63    /// Creates a new `BinaryOp` from two expressions and an operator.
64    pub fn new<L, R>(lhs_expr: L, operator: BinaryOperator, rhs_expr: R) -> BinaryOp
65    where
66        L: Into<Expression>,
67        R: Into<Expression>,
68    {
69        BinaryOp {
70            lhs_expr: lhs_expr.into(),
71            operator,
72            rhs_expr: rhs_expr.into(),
73        }
74    }
75
76    // Normalize binary operation following operator precedence rules.
77    //
78    // The result can be evaluated from left to right without checking operator precendence.
79    pub(crate) fn normalize(self) -> BinaryOp {
80        use Operand::{BinOp, Expr};
81
82        // We only care whether the operand is another binary operation or not. Any other
83        // expression (including unary oparations) is treated the same way and does not require
84        // special precedence rules.
85        enum Operand {
86            BinOp(BinaryOp),
87            Expr(Expression),
88        }
89
90        impl From<Expression> for Operand {
91            fn from(expr: Expression) -> Self {
92                match expr {
93                    Expression::Operation(operation) => match *operation {
94                        Operation::Binary(binary) => Operand::BinOp(binary),
95                        unary => Operand::Expr(Expression::from(unary)),
96                    },
97                    expr => Operand::Expr(expr),
98                }
99            }
100        }
101
102        let lhs = Operand::from(self.lhs_expr);
103        let operator = self.operator;
104        let rhs = Operand::from(self.rhs_expr);
105
106        match (lhs, rhs) {
107            (BinOp(lhs), BinOp(rhs)) => normalize_both(lhs.normalize(), operator, rhs.normalize()),
108            (BinOp(lhs), Expr(rhs)) => normalize_lhs(lhs.normalize(), operator, rhs),
109            (Expr(lhs), BinOp(rhs)) => normalize_rhs(lhs, operator, rhs.normalize()),
110            (Expr(lhs), Expr(rhs)) => BinaryOp::new(lhs, operator, rhs),
111        }
112    }
113}
114
115fn normalize_both(lhs: BinaryOp, operator: BinaryOperator, rhs: BinaryOp) -> BinaryOp {
116    if lhs.operator.precedence() < operator.precedence() {
117        // BinaryOp(BinaryOp(lhs.lhs_expr + lhs.rhs_expr) * BinaryOp(rhs.lhs_expr - rhs.rhs_expr))
118        //
119        // => BinaryOp(lhs.lhs_expr + BinaryOp(BinaryOp(lhs.rhs_expr * rhs.lhs_expr) - rhs.rhs_expr))
120        BinaryOp::new(
121            lhs.lhs_expr,
122            lhs.operator,
123            Operation::Binary(normalize_rhs(lhs.rhs_expr, operator, rhs)),
124        )
125    } else if rhs.operator.precedence() < operator.precedence() {
126        // BinaryOp(BinaryOp(lhs.lhs_expr / lhs.rhs_expr) * BinaryOp(rhs.lhs_expr - rhs.rhs_expr))
127        //
128        // => BinaryOp(BinaryOp(BinaryOp(lhs.lhs_expr / lhs.rhs_expr) * rhs.lhs_expr) - rhs.rhs_expr)
129        BinaryOp::new(
130            Operation::Binary(normalize_lhs(lhs, operator, rhs.lhs_expr)),
131            rhs.operator,
132            rhs.rhs_expr,
133        )
134    } else {
135        // Nothing to normalize.
136        BinaryOp::new(Operation::Binary(lhs), operator, Operation::Binary(rhs))
137    }
138}
139
140fn normalize_lhs(lhs: BinaryOp, operator: BinaryOperator, rhs_expr: Expression) -> BinaryOp {
141    if lhs.operator.precedence() < operator.precedence() {
142        // BinaryOp(BinaryOp(lhs.lhs_expr + lhs.rhs_expr) / rhs_expr)
143        //
144        // => BinaryOp(lhs.lhs_expr + BinaryOp(lhs.rhs_expr / rhs_expr))
145        BinaryOp::new(
146            lhs.lhs_expr,
147            lhs.operator,
148            Operation::Binary(BinaryOp::new(lhs.rhs_expr, operator, rhs_expr)),
149        )
150    } else {
151        // Nothing to normalize.
152        BinaryOp::new(Operation::Binary(lhs), operator, rhs_expr)
153    }
154}
155
156fn normalize_rhs(lhs_expr: Expression, operator: BinaryOperator, rhs: BinaryOp) -> BinaryOp {
157    if rhs.operator.precedence() < operator.precedence() {
158        // BinaryOp(lhs_expr / BinaryOp(rhs.lhs_expr + rhs.rhs_expr))
159        //
160        // => BinaryOp(BinaryOp(lhs_expr / rhs.lhs_expr) + rhs.rhs_expr)
161        BinaryOp::new(
162            Operation::Binary(BinaryOp::new(lhs_expr, operator, rhs.lhs_expr)),
163            rhs.operator,
164            rhs.rhs_expr,
165        )
166    } else {
167        // Nothing to normalize.
168        BinaryOp::new(lhs_expr, operator, Operation::Binary(rhs))
169    }
170}
171
172#[cfg(test)]
173mod tests {
174    use super::*;
175    use pretty_assertions::assert_eq;
176
177    macro_rules! binop {
178        ($l:expr, $op:expr, $r:expr $(,)?) => {
179            BinaryOp::new($l, $op, $r)
180        };
181    }
182
183    macro_rules! assert_normalizes_to {
184        ($op:expr, $expected:expr $(,)?) => {
185            assert_eq!($op.normalize(), $expected);
186        };
187    }
188
189    #[test]
190    fn normalize_binary_op() {
191        use BinaryOperator::{Div, Mod, Mul, Plus};
192
193        assert_normalizes_to!(
194            binop!(binop!(1, Plus, 2), Div, binop!(3, Mul, 4)),
195            binop!(1, Plus, binop!(2, Div, binop!(3, Mul, 4))),
196        );
197
198        assert_normalizes_to!(
199            binop!(binop!(1, Div, 2), Mul, binop!(3, Plus, binop!(4, Mod, 5))),
200            binop!(binop!(binop!(1, Div, 2), Mul, 3), Plus, binop!(4, Mod, 5)),
201        );
202
203        assert_normalizes_to!(
204            binop!(binop!(binop!(1, Plus, 2), Mul, 3), Div, 4),
205            binop!(1, Plus, binop!(binop!(2, Mul, 3), Div, 4)),
206        );
207
208        assert_normalizes_to!(
209            binop!(1, Div, binop!(binop!(2, Plus, 3), Mul, 4)),
210            binop!(binop!(1, Div, 2), Plus, binop!(3, Mul, 4)),
211        );
212    }
213
214    #[test]
215    fn normalize_parenthesized() {
216        use BinaryOperator::{Div, Mod, Mul, Plus};
217
218        fn parens(op: BinaryOp) -> Expression {
219            Expression::Parenthesis(Box::new(op.into()))
220        }
221
222        let op = binop!(
223            parens(binop!(1, Div, 2)),
224            Mul,
225            parens(binop!(3, Mod, parens(binop!(4, Plus, 5)))),
226        );
227
228        assert_normalizes_to!(op.clone(), op);
229    }
230
231    #[test]
232    fn already_normalized() {
233        use BinaryOperator::Plus;
234
235        let op = binop!(binop!(1, Plus, 2), Plus, binop!(3, Plus, 4));
236
237        assert_normalizes_to!(op.clone(), op);
238    }
239}