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
use super::{compound_from_ast, expression::rvalue_from_ast, IRGeneratingContext};
use crate::{
    ast,
    ir::statement::{branch::BranchType, Branch, Jump},
};

/// Generate IR from [`ast::statement::While`] AST node.
pub fn from_ast(ast: &ast::statement::While, ctx: &mut IRGeneratingContext) {
    let ast::statement::While { condition, content } = ast;
    let statement_id = ctx.parent_context.next_loop_id;
    ctx.parent_context.next_loop_id += 1;
    let condition_label = format!("loop_{statement_id}_condition");
    let success_label = format!("loop_{statement_id}_success");
    let fail_label = format!("loop_{statement_id}_fail");
    ctx.end_current_basic_block_with(Jump {
        label: condition_label.clone(),
    });
    ctx.current_basic_block.name = Some(condition_label.clone());
    let condition_register = rvalue_from_ast(condition, ctx);
    ctx.end_current_basic_block_with(Branch {
        branch_type: BranchType::NE,
        operand1: condition_register,
        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);
    if !ctx.current_basic_block.empty() {
        ctx.end_current_basic_block_with(Jump {
            label: condition_label,
        });
    }
    ctx.current_basic_block.name = Some(fail_label);
}

#[cfg(test)]
mod tests {
    #![allow(clippy::borrow_interior_mutable_const)]
    use super::*;
    use crate::{
        ast::expression::{IntegerLiteral, VariableRef},
        ir::statement::Ret,
        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::While {
            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()]),
        };
        from_ast(&ast, &mut ctx);
        let basic_blocks = ctx.done();
        assert_eq!(basic_blocks.len(), 4);
        assert_eq!(basic_blocks[1].name.as_ref().unwrap(), "loop_0_condition");
        assert_eq!(
            basic_blocks[1].content.last().unwrap().clone(),
            Branch {
                branch_type: BranchType::NE,
                operand1: 42.into(),
                operand2: 0.into(),
                success_label: "loop_0_success".to_string(),
                failure_label: "loop_0_fail".to_string(),
            }
            .into()
        );
        assert_eq!(basic_blocks[2].name.as_ref().unwrap(), "loop_0_success");
        assert_eq!(basic_blocks[2].content.len(), 2);
        assert_eq!(
            basic_blocks[2].content.last().unwrap().clone(),
            Jump {
                label: "loop_0_condition".to_string(),
            }
            .into()
        );
        assert_eq!(basic_blocks[3].name.as_ref().unwrap(), "loop_0_fail");
        assert_eq!(
            basic_blocks[3].content.last().unwrap().clone(),
            Ret { value: None }.into()
        );
    }
}