use super::Expression;
use serde::Deserialize;
#[doc(inline)]
pub use hcl_primitives::expr::{BinaryOperator, UnaryOperator};
#[derive(Deserialize, Debug, PartialEq, Eq, Clone)]
pub enum Operation {
Unary(UnaryOp),
Binary(BinaryOp),
}
impl From<UnaryOp> for Operation {
fn from(op: UnaryOp) -> Self {
Operation::Unary(op)
}
}
impl From<BinaryOp> for Operation {
fn from(op: BinaryOp) -> Self {
Operation::Binary(op)
}
}
#[derive(Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct UnaryOp {
pub operator: UnaryOperator,
pub expr: Expression,
}
impl UnaryOp {
pub fn new<T>(operator: UnaryOperator, expr: T) -> UnaryOp
where
T: Into<Expression>,
{
UnaryOp {
operator,
expr: expr.into(),
}
}
}
#[derive(Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct BinaryOp {
pub lhs_expr: Expression,
pub operator: BinaryOperator,
pub rhs_expr: Expression,
}
impl BinaryOp {
pub fn new<L, R>(lhs_expr: L, operator: BinaryOperator, rhs_expr: R) -> BinaryOp
where
L: Into<Expression>,
R: Into<Expression>,
{
BinaryOp {
lhs_expr: lhs_expr.into(),
operator,
rhs_expr: rhs_expr.into(),
}
}
pub(crate) fn normalize(self) -> BinaryOp {
use Operand::{BinOp, Expr};
enum Operand {
BinOp(BinaryOp),
Expr(Expression),
}
impl From<Expression> for Operand {
fn from(expr: Expression) -> Self {
match expr {
Expression::Operation(operation) => match *operation {
Operation::Binary(binary) => Operand::BinOp(binary),
unary => Operand::Expr(Expression::from(unary)),
},
expr => Operand::Expr(expr),
}
}
}
let lhs = Operand::from(self.lhs_expr);
let operator = self.operator;
let rhs = Operand::from(self.rhs_expr);
match (lhs, rhs) {
(BinOp(lhs), BinOp(rhs)) => normalize_both(lhs.normalize(), operator, rhs.normalize()),
(BinOp(lhs), Expr(rhs)) => normalize_lhs(lhs.normalize(), operator, rhs),
(Expr(lhs), BinOp(rhs)) => normalize_rhs(lhs, operator, rhs.normalize()),
(Expr(lhs), Expr(rhs)) => BinaryOp::new(lhs, operator, rhs),
}
}
}
fn normalize_both(lhs: BinaryOp, operator: BinaryOperator, rhs: BinaryOp) -> BinaryOp {
if lhs.operator.precedence() < operator.precedence() {
BinaryOp::new(
lhs.lhs_expr,
lhs.operator,
Operation::Binary(normalize_rhs(lhs.rhs_expr, operator, rhs)),
)
} else if rhs.operator.precedence() < operator.precedence() {
BinaryOp::new(
Operation::Binary(normalize_lhs(lhs, operator, rhs.lhs_expr)),
rhs.operator,
rhs.rhs_expr,
)
} else {
BinaryOp::new(Operation::Binary(lhs), operator, Operation::Binary(rhs))
}
}
fn normalize_lhs(lhs: BinaryOp, operator: BinaryOperator, rhs_expr: Expression) -> BinaryOp {
if lhs.operator.precedence() < operator.precedence() {
BinaryOp::new(
lhs.lhs_expr,
lhs.operator,
Operation::Binary(BinaryOp::new(lhs.rhs_expr, operator, rhs_expr)),
)
} else {
BinaryOp::new(Operation::Binary(lhs), operator, rhs_expr)
}
}
fn normalize_rhs(lhs_expr: Expression, operator: BinaryOperator, rhs: BinaryOp) -> BinaryOp {
if rhs.operator.precedence() < operator.precedence() {
BinaryOp::new(
Operation::Binary(BinaryOp::new(lhs_expr, operator, rhs.lhs_expr)),
rhs.operator,
rhs.rhs_expr,
)
} else {
BinaryOp::new(lhs_expr, operator, Operation::Binary(rhs))
}
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
macro_rules! binop {
($l:expr, $op:expr, $r:expr $(,)?) => {
BinaryOp::new($l, $op, $r)
};
}
macro_rules! assert_normalizes_to {
($op:expr, $expected:expr $(,)?) => {
assert_eq!($op.normalize(), $expected);
};
}
#[test]
fn normalize_binary_op() {
use BinaryOperator::{Div, Mod, Mul, Plus};
assert_normalizes_to!(
binop!(binop!(1, Plus, 2), Div, binop!(3, Mul, 4)),
binop!(1, Plus, binop!(2, Div, binop!(3, Mul, 4))),
);
assert_normalizes_to!(
binop!(binop!(1, Div, 2), Mul, binop!(3, Plus, binop!(4, Mod, 5))),
binop!(binop!(binop!(1, Div, 2), Mul, 3), Plus, binop!(4, Mod, 5)),
);
assert_normalizes_to!(
binop!(binop!(binop!(1, Plus, 2), Mul, 3), Div, 4),
binop!(1, Plus, binop!(binop!(2, Mul, 3), Div, 4)),
);
assert_normalizes_to!(
binop!(1, Div, binop!(binop!(2, Plus, 3), Mul, 4)),
binop!(binop!(1, Div, 2), Plus, binop!(3, Mul, 4)),
);
}
#[test]
fn normalize_parenthesized() {
use BinaryOperator::{Div, Mod, Mul, Plus};
fn parens(op: BinaryOp) -> Expression {
Expression::Parenthesis(Box::new(op.into()))
}
let op = binop!(
parens(binop!(1, Div, 2)),
Mul,
parens(binop!(3, Mod, parens(binop!(4, Plus, 5)))),
);
assert_normalizes_to!(op.clone(), op);
}
#[test]
fn already_normalized() {
use BinaryOperator::Plus;
let op = binop!(binop!(1, Plus, 2), Plus, binop!(3, Plus, 4));
assert_normalizes_to!(op.clone(), op);
}
}