gbf_core/decompiler/ast/
unary_op.rs

1#![deny(missing_docs)]
2
3use gbf_macros::AstNodeTransform;
4use serde::{Deserialize, Serialize};
5
6use crate::define_ast_enum_type;
7
8use super::{
9    AstKind, AstNodeError, AstVisitable, expr::ExprKind, literal::LiteralNode, ptr::P,
10    visitors::AstVisitor,
11};
12
13define_ast_enum_type!(
14    UnaryOpType {
15        LogicalNot => "!",
16        BitwiseNot => "~",
17        Negate => "-",
18    }
19);
20
21/// Represents a unary operation node in the AST, such as `-a` or `!b`.
22#[derive(Debug, Clone, Serialize, Deserialize, Eq, AstNodeTransform)]
23#[convert_to(ExprKind::UnaryOp, AstKind::Expression)]
24pub struct UnaryOperationNode {
25    /// The operand of the unary operation.
26    pub operand: ExprKind,
27    /// The unary operation type.
28    pub op_type: UnaryOpType,
29}
30
31impl UnaryOperationNode {
32    /// Creates a new `UnaryOperationNode` after validating the operand.
33    ///
34    /// # Arguments
35    /// - `operand` - The operand for the unary operation.
36    /// - `op_type` - The unary operation type.
37    ///
38    /// # Returns
39    /// A new `UnaryOperationNode`.
40    ///
41    /// # Errors
42    /// Returns an `AstNodeError` if `operand` is of an unsupported type.
43    pub fn new(operand: ExprKind, op_type: UnaryOpType) -> Result<Self, AstNodeError> {
44        Self::validate_operand(&operand)?;
45
46        Ok(Self { operand, op_type })
47    }
48
49    fn validate_operand(expr: &ExprKind) -> Result<(), AstNodeError> {
50        // Most expressions are ok except for string literals.
51        if let ExprKind::Literal(lit) = expr {
52            if let LiteralNode::String(_) = lit.as_ref() {
53                return Err(AstNodeError::InvalidOperand);
54            }
55        }
56        Ok(())
57    }
58}
59
60impl AstVisitable for P<UnaryOperationNode> {
61    fn accept<V: AstVisitor>(&self, visitor: &mut V) -> V::Output {
62        visitor.visit_unary_op(self)
63    }
64}
65
66// == Other implementations for unary operations ==
67impl PartialEq for UnaryOperationNode {
68    fn eq(&self, other: &Self) -> bool {
69        self.operand == other.operand && self.op_type == other.op_type
70    }
71}
72
73#[cfg(test)]
74mod tests {
75    use crate::decompiler::ast::{
76        AstNodeError, bin_op::BinOpType, emit, new_bin_op, new_id, new_str, new_unary_op,
77    };
78
79    use super::UnaryOpType;
80
81    #[test]
82    fn test_unary_op_emit() -> Result<(), AstNodeError> {
83        for op_type in UnaryOpType::all_variants() {
84            let expr = new_unary_op(new_id("a"), op_type.clone())?;
85            assert_eq!(emit(expr), format!("{}a", op_type.as_str()));
86        }
87        Ok(())
88    }
89
90    #[test]
91    fn test_nested_unary_op_emit() -> Result<(), AstNodeError> {
92        for op_type in UnaryOpType::all_variants() {
93            let expr = new_unary_op(new_unary_op(new_id("a"), op_type.clone())?, op_type.clone())?;
94            assert_eq!(
95                emit(expr),
96                format!("{}({}a)", op_type.as_str(), op_type.as_str())
97            );
98        }
99        Ok(())
100    }
101
102    #[test]
103    fn test_unary_op_binary_operand() -> Result<(), AstNodeError> {
104        let result = new_unary_op(
105            new_bin_op(new_id("a"), new_id("b"), BinOpType::Add)?,
106            UnaryOpType::Negate,
107        )?;
108
109        assert_eq!(emit(result), "-(a + b)");
110
111        Ok(())
112    }
113
114    #[test]
115    fn test_unary_op_invalid_operand() {
116        let result = new_unary_op(new_str("a"), UnaryOpType::Negate);
117        assert!(result.is_err());
118    }
119
120    #[test]
121    fn test_unary_op_equality() -> Result<(), AstNodeError> {
122        let unary1 = new_unary_op(new_id("a"), UnaryOpType::Negate)?;
123        let unary2 = new_unary_op(new_id("a"), UnaryOpType::Negate)?;
124        assert_eq!(unary1, unary2);
125
126        let unary3 = new_unary_op(new_id("b"), UnaryOpType::Negate)?;
127        assert_ne!(unary1, unary3);
128        Ok(())
129    }
130}