1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
use super::{compound_from_ast, expression, IRGeneratingContext};
use crate::{
    ast,
    ir::statement::{branch::BranchType, Branch, Jump},
};

/// Generates IR for an if statement.
pub fn from_ast(ast: &ast::statement::If, ctx: &mut IRGeneratingContext) {
    let ast::statement::If {
        condition,
        content,
        else_content,
    } = ast;

    let if_id = ctx.parent_context.next_if_id;
    ctx.parent_context.next_if_id += 1;
    let success_label = format!("if_{if_id}_success");
    let fail_label = format!("if_{if_id}_fail");
    let end_label = format!("if_{if_id}_end");
    let condition = expression::rvalue_from_ast(condition, ctx);
    ctx.end_current_basic_block_with(Branch {
        branch_type: BranchType::NE,
        operand1: condition,
        operand2: 0.into(),
        success_label: success_label.clone(),
        failure_label: fail_label.clone(),
    });
    ctx.current_basic_block.name = Some(success_label);
    compound_from_ast(content, ctx);
    // it is possible that last BasicBlock has already end
    if !ctx.current_basic_block.empty() {
        ctx.end_current_basic_block_with(Jump {
            label: end_label.clone(),
        });
    }
    ctx.current_basic_block.name = Some(fail_label);
    if let Some(else_content) = else_content {
        compound_from_ast(else_content, ctx);
    }
    if !ctx.current_basic_block.empty() {
        ctx.end_current_basic_block_with(Jump {
            label: end_label.clone(),
        });
    }
    ctx.current_basic_block.name = Some(end_label);
}

#[cfg(test)]
mod tests {
    #![allow(clippy::borrow_interior_mutable_const)]
    use super::*;
    use crate::{
        ast::expression::{IntegerLiteral, VariableRef},
        ir::statement,
        utility::data_type,
    };

    #[test]
    fn test_from_ast() {
        let mut parent_ctx = crate::ir::IRGeneratingContext::new();
        let mut ctx = IRGeneratingContext::new(&mut parent_ctx);
        ctx.symbol_table
            .variable_types_stack
            .last_mut()
            .unwrap()
            .insert(VariableRef("a".to_string()), (data_type::I32.clone(), 0));
        ctx.symbol_table
            .variable_types_stack
            .last_mut()
            .unwrap()
            .insert(VariableRef("b".to_string()), (data_type::I32.clone(), 0));
        let ast = ast::statement::If {
            condition: IntegerLiteral(42).into(),
            content: ast::statement::compound::Compound(vec![ast::statement::Assign {
                lhs: ast::expression::lvalue::LValue::VariableRef(
                    ast::expression::variable_ref::VariableRef("a".to_string()),
                ),
                rhs: IntegerLiteral(42).into(),
            }
            .into()]),
            else_content: Some(ast::statement::compound::Compound(vec![
                ast::statement::Assign {
                    lhs: ast::expression::lvalue::LValue::VariableRef(
                        ast::expression::variable_ref::VariableRef("b".to_string()),
                    ),
                    rhs: IntegerLiteral(42).into(),
                }
                .into(),
            ])),
        };
        from_ast(&ast, &mut ctx);
        let basic_blocks = ctx.done();
        assert_eq!(basic_blocks.len(), 4);
        assert_eq!(basic_blocks[0].content.len(), 1);
        assert_eq!(
            basic_blocks[0].content[0].clone(),
            statement::Branch {
                branch_type: crate::ir::statement::branch::BranchType::NE,
                operand1: 42.into(),
                operand2: 0.into(),
                success_label: "if_0_success".to_string(),
                failure_label: "if_0_fail".to_string(),
            }
            .into()
        );
        assert_eq!(basic_blocks[1].name.as_ref().unwrap(), "if_0_success");
        assert_eq!(basic_blocks[1].content.len(), 2);
        assert_eq!(
            basic_blocks[1].content[1].clone(),
            statement::Jump {
                label: "if_0_end".to_string(),
            }
            .into()
        );
        assert_eq!(basic_blocks[2].name.as_ref().unwrap(), "if_0_fail");
        assert_eq!(basic_blocks[2].content.len(), 2);
        assert_eq!(
            basic_blocks[2].content[1].clone(),
            statement::Jump {
                label: "if_0_end".to_string(),
            }
            .into()
        );
        assert_eq!(basic_blocks[3].name.as_ref().unwrap(), "if_0_end");
    }
}