def visit(self, node: ast.ConditionalNode, scope: Scope):
        """
        <local vars>
        if_dest = <if_expr>
        IF if_dest GOTO then
        GOTO else
        LABEL then
        then_dest = <then_expr>
        cond_res = then_dest
        GOTO endif
        LABEL else
        else_dest = <else_expr>
        cond_res = else_dest
        LABEL endif
        """
        self.add_comment('Conditional if-else')

        then_label = self.new_label('then')
        else_label = self.new_label('else')
        endif_label = self.new_label('endif')

        cond_res = self.add_local('cond_res')
        if_dest = self.visit(node.if_expr, scope)
        self.add_inst(cil.GotoIfNode(if_dest, then_label.name))
        self.add_inst(cil.GotoNode(else_label.name))
        self.add_inst(then_label)
        then_dest = self.visit(node.then_expr, scope)
        self.add_inst(cil.AssignNode(cond_res, then_dest))
        self.add_inst(cil.GotoNode(endif_label.name))
        self.add_inst(else_label)
        else_dest = self.visit(node.else_expr, scope)
        self.add_inst(cil.AssignNode(cond_res, else_dest))
        self.add_inst(endif_label)
        return cond_res
    def visit(self, node: ast.ClassDeclarationNode, scope: Scope):
        self.current_type = node.id
        init = cil.FunctionNode(name=f'{node.id}__init',
                                params=[],
                                local_vars=[
                                    cil.LocalNode('self'),
                                ],
                                instructions=[
                                    cil.InitNode('self', node.id),
                                ])
        self.root.dot_code.append(init)
        self.current_init = init

        self.current_function = self.current_init
        type_node = self.root.get_type(self.current_type)
        for attr_name in type_node.attributes:
            attr = self.add_local(extract_feat_name(attr_name), internal=False)
            attr_expr, attr_scope = type_node.get_attr_node(attr_name)
            attr_value = self.visit(attr_expr, attr_scope)
            attr_index = type_node.attributes.index(attr_name)
            attr_at = cil.AttributeAt(attr_name, attr_index)
            self.add_inst(cil.SetAttrNode('self', attr_at, attr_value))
            self.add_inst(cil.AssignNode(attr, attr_value))

        for feat in node.features:
            # if isinstance(feat, ast.AttrDeclarationNode):
            #     visited_attrs.append(feat.id)
            #     self.visit(feat, scope)
            if isinstance(feat, ast.FuncDeclarationNode):
                tagged_scope = scope.get_tagged_scope(feat.id)
                self.visit(feat, tagged_scope)

        init.instructions.append(cil.ReturnNode('self'))
 def visit(self, node: ast.AssignNode, scope: Scope):
     expr_dest = self.visit(node.expr, scope)
     self.add_inst(cil.AssignNode(node.id, expr_dest))
     variable = scope.find_variable(node.id)
     if variable.is_attr:
         attr_name = f'{self.current_type}_{node.id}'
         attr_index = self.root.get_type(
             self.current_type).attributes.index(attr_name)
         attr_at = cil.AttributeAt(attr_name, attr_index)
         self.add_inst(cil.SetAttrNode('self', attr_at, value=expr_dest))
     return expr_dest
 def visit(self, node: ast.LetDeclarationNode, scope: Scope):
     local = self.add_local(node.id, internal=False)
     if node.expr is not None:
         expr_node = node.expr
     elif node.type == 'String':
         expr_node = ast.StringNode('""')
     elif node.type == 'Bool':
         expr_node = ast.BooleanNode('false')
     elif node.type == 'Int':
         expr_node = ast.IntegerNode('0')
     else:
         expr_node = ast.VariableNode('void')
     expr_dest = self.visit(expr_node, scope)
     self.add_inst(cil.AssignNode(local, expr_dest))
class DotCodeVisitor:
    """
    Builds the .CODE section.
    """
    def __init__(self, cil_root: cil.ProgramNode, context: Context):
        super().__init__()
        self.root = cil_root
        self.code = cil_root.dot_code
        self.current_function: cil.FunctionNode | None = None
        self.current_type: str | None = None
        self.current_init: cil.FunctionNode | None = None
        self.label_count = -1
        self.context = context

    def new_label(self, name: str) -> cil.LabelNode:
        self.label_count += 1
        name = f'_{name}_{self.label_count}'
        return cil.LabelNode(name)

    def add_function(self, name: str):
        # if name is None:
        #     name = f'f{self.next_function_id}'
        self.current_function = cil.FunctionNode(name, [], [], [])
        self.code.append(self.current_function)

    def add_param(self, name: str) -> str:
        param = cil.ParamNode(name)
        self.current_function.params.append(param)
        return name

    def add_local(self, name: str, internal: bool = True) -> str:
        if internal:
            name = f'_{name}_{len(self.current_function.local_vars)}'
        local = cil.LocalNode(name)
        self.current_function.local_vars.append(local)
        return name

    def add_inst(self, inst: cil.InstructionNode) -> cil.InstructionNode:
        self.current_function.instructions.append(inst)
        return inst

    def add_comment(self, text: str):
        self.add_inst(cil.CommentNode(text))

    @property
    def current_is_init(self):
        return self.current_function.name == self.current_init.name

    @visitor.on('node')
    def visit(self, node: ast.Node, scope: Scope):
        raise NotImplementedError()

    @visitor.when(ast.ProgramNode)
    def visit(self, node: ast.ProgramNode, scope: Scope):
        # the root scope stores void to avoid semantic errors initializing
        # the void attribute of classes to void. After that every function
        # has access to void through that attribute in every class.
        # So, pop it to avoid repeated locals.
        scope.locals.pop(0)
        # build the code functions
        for class_ in node.declarations:
            tagged_scope = scope.get_tagged_scope(class_.id)

            self.visit(class_, deepcopy(tagged_scope))

            # build the entry function:
            for class_ in node.declarations:
                if class_.id == 'Main':
                    for feature in class_.features:
                        if isinstance(feature, ast.FuncDeclarationNode
                                      ) and feature.id == 'main':
                            self.add_function('main')
                            instance = self.add_local('main_instance')
                            self.add_inst(
                                cil.StaticCallNode('Main__init', instance))
                            self.add_comment('Calling main')
                            result = self.add_local('result')
                            self.add_inst(cil.ArgNode(instance))
                            self.add_inst(
                                cil.DynamicCallNode(instance, 'main', result,
                                                    None, 'Main'))
                            self.add_inst(cil.ReturnNode(0))
                            break

        # add the default functions of COOL
        # TODO: add missing instructions
        self.code += [
            cil.FunctionNode(name='Object__init',
                             params=[],
                             local_vars=[
                                 cil.LocalNode('self'),
                             ],
                             instructions=[
                                 cil.InitNode('self', 'Object'),
                                 cil.ReturnNode('self')
                             ]),
            cil.FunctionNode(
                name='Object_abort',
                params=[cil.ParamNode('self'),
                        cil.ParamNode('typename')],
                local_vars=[],
                instructions=[
                    cil.AbortNode(),
                    cil.ReturnNode(),
                ]),
            cil.FunctionNode(name='Object_type_name',
                             params=[
                                 cil.ParamNode('self'),
                             ],
                             local_vars=[
                                 cil.LocalNode('name'),
                             ],
                             instructions=[
                                 cil.TypeNameNode('name', 'self'),
                                 cil.ReturnNode('name'),
                             ]),
            cil.FunctionNode(name='Object_copy',
                             params=[
                                 cil.ParamNode('self'),
                             ],
                             local_vars=[
                                 cil.LocalNode('self_copy'),
                             ],
                             instructions=[
                                 cil.AssignNode('self_copy', 'self'),
                                 cil.ReturnNode('self_copy'),
                             ]),
            cil.FunctionNode(name='IO__init',
                             params=[],
                             local_vars=[
                                 cil.LocalNode('self'),
                             ],
                             instructions=[
                                 cil.InitNode('self', 'IO'),
                                 cil.ReturnNode('self'),
                             ]),
            cil.FunctionNode(name='IO_out_string',
                             params=[
                                 cil.ParamNode('self'),
                                 cil.ParamNode('str_addr'),
                             ],
                             local_vars=[],
                             instructions=[
                                 cil.PrintStringNode('str_addr'),
                                 cil.ReturnNode('self'),
                             ]),
            cil.FunctionNode(name='IO_out_int',
                             params=[
                                 cil.ParamNode('self'),
                                 cil.ParamNode('int_addr'),
                             ],
                             local_vars=[],
                             instructions=[
                                 cil.PrintIntNode('int_addr'),
                                 cil.ReturnNode('self'),
                             ]),
            cil.FunctionNode(name='IO_in_string',
                             params=[
                                 cil.ParamNode('self'),
                             ],
                             local_vars=[
                                 cil.LocalNode('_in_string'),
                             ],
                             instructions=[
                                 cil.ReadStringNode('_in_string'),
                                 cil.ReturnNode('_in_string'),
                             ]),
            cil.FunctionNode(name='IO_in_int',
                             params=[
                                 cil.ParamNode('self'),
                             ],
                             local_vars=[
                                 cil.LocalNode('_in_int'),
                             ],
                             instructions=[
                                 cil.ReadIntNode('_in_int'),
                                 cil.ReturnNode('_in_int'),
                             ]),
            cil.FunctionNode(name='String__init',
                             params=[],
                             local_vars=[
                                 cil.LocalNode('self'),
                             ],
                             instructions=[
                                 cil.InitNode('self', 'String'),
                                 cil.ReturnNode('self'),
                             ]),
            cil.FunctionNode(name='String_length',
                             params=[
                                 cil.ParamNode('self'),
                             ],
                             local_vars=[
                                 cil.LocalNode('_length'),
                             ],
                             instructions=[
                                 cil.LengthNode('self', '_length'),
                                 cil.ReturnNode('_length'),
                             ]),
            cil.FunctionNode(name='String_concat',
                             params=[
                                 cil.ParamNode('self'),
                                 cil.ParamNode('other_str'),
                             ],
                             local_vars=[
                                 cil.LocalNode('result'),
                             ],
                             instructions=[
                                 cil.ConcatNode('result', 'self', 'other_str'),
                                 cil.ReturnNode('result'),
                             ]),
            cil.FunctionNode(name='String_substr',
                             params=[
                                 cil.ParamNode('self'),
                                 cil.ParamNode('index'),
                                 cil.ParamNode('length'),
                             ],
                             local_vars=[
                                 cil.LocalNode('result'),
                             ],
                             instructions=[
                                 cil.SubstringNode('result', 'value', 'index',
                                                   'length'),
                                 cil.ReturnNode('result'),
                             ]),
            cil.FunctionNode(name='Bool__init',
                             params=[],
                             local_vars=[
                                 cil.LocalNode('self'),
                             ],
                             instructions=[
                                 cil.InitNode('self', 'Bool'),
                                 cil.ReturnNode('self'),
                             ]),
            cil.FunctionNode(name='Int__init',
                             params=[],
                             local_vars=[
                                 cil.LocalNode('self'),
                             ],
                             instructions=[
                                 cil.InitNode('self', 'Int'),
                                 cil.ReturnNode('self'),
                             ]),
            cil.FunctionNode(name='Void__init',
                             params=[],
                             local_vars=[
                                 cil.LocalNode('self'),
                             ],
                             instructions=[
                                 cil.InitNode('self', 'Void'),
                                 cil.ReturnNode('self'),
                             ]),
        ]

    @visitor.when(ast.ClassDeclarationNode)
    def visit(self, node: ast.ClassDeclarationNode, scope: Scope):
        self.current_type = node.id
        init = cil.FunctionNode(name=f'{node.id}__init',
                                params=[],
                                local_vars=[
                                    cil.LocalNode('self'),
                                ],
                                instructions=[
                                    cil.InitNode('self', node.id),
                                ])
        self.root.dot_code.append(init)
        self.current_init = init

        self.current_function = self.current_init
        type_node = self.root.get_type(self.current_type)
        for attr_name in type_node.attributes:
            attr = self.add_local(extract_feat_name(attr_name), internal=False)
            attr_expr, attr_scope = type_node.get_attr_node(attr_name)
            attr_value = self.visit(attr_expr, attr_scope)
            attr_index = type_node.attributes.index(attr_name)
            attr_at = cil.AttributeAt(attr_name, attr_index)
            self.add_inst(cil.SetAttrNode('self', attr_at, attr_value))
            self.add_inst(cil.AssignNode(attr, attr_value))

        for feat in node.features:
            # if isinstance(feat, ast.AttrDeclarationNode):
            #     visited_attrs.append(feat.id)
            #     self.visit(feat, scope)
            if isinstance(feat, ast.FuncDeclarationNode):
                tagged_scope = scope.get_tagged_scope(feat.id)
                self.visit(feat, tagged_scope)

        init.instructions.append(cil.ReturnNode('self'))

    # @visitor.when(ast.AttrDeclarationNode)
    # def visit(self, node: ast.AttrDeclarationNode, scope: Scope):
    #     self.current_function = self.current_init
    #     attr = self.add_local(node.id, internal=False)
    #
    #     attr_index = self.root.get_type(self.current_type).attributes.index(f'{self.current_type}_{node.id}')
    #     if node.expr is not None:
    #         result = self.visit(node.expr, scope)
    #         self.add_inst(cil.SetAttrNode('self', cil.AttributeAt(attr, attr_index), result))
    #         self.add_inst(cil.AssignNode(attr, result))
    #     else:
    #         type_node = self.root.get_type(self.current_type)
    #         attr_expr = type_node.get_attr_node(node.id)
    #         attr_value = self.visit(attr_expr, scope)
    #         attr_index = type_node.attributes.index(node.id)
    #         attr_at = cil.AttributeAt(node.id, attr_index)
    #         self.add_inst(
    #             cil.SetAttrNode('self', attr_at, attr_value)
    #         )

    @visitor.when(ast.FuncDeclarationNode)
    def visit(self, node: ast.FuncDeclarationNode, scope: Scope):
        self.add_function(f'{self.current_type}_{node.id}')

        for local in scope.all_locals():
            if local.is_param:
                self.add_param(local.name)
            else:
                local_name = self.add_local(local.name, internal=False)
                if local.is_attr:
                    attr_name = f'{self.current_type}_{local.name}'
                    self.add_inst(
                        cil.GetAttrNode(local_name, 'self', attr_name))

        result = self.visit(node.body, scope)
        self.add_inst(cil.ReturnNode(result))

    @visitor.when(ast.LetDeclarationNode)
    def visit(self, node: ast.LetDeclarationNode, scope: Scope):
        local = self.add_local(node.id, internal=False)
        if node.expr is not None:
            expr_node = node.expr
        elif node.type == 'String':
            expr_node = ast.StringNode('""')
        elif node.type == 'Bool':
            expr_node = ast.BooleanNode('false')
        elif node.type == 'Int':
            expr_node = ast.IntegerNode('0')
        else:
            expr_node = ast.VariableNode('void')
        expr_dest = self.visit(expr_node, scope)
        self.add_inst(cil.AssignNode(local, expr_dest))

    @visitor.when(ast.LetNode)
    def visit(self, node: ast.LetNode, scope: Scope):
        let_scope = scope.children.pop(0)
        for let_declaration in node.declarations:
            self.visit(let_declaration, let_scope)

        return self.visit(node.expr, let_scope)

    @visitor.when(ast.ParenthesisExpr)
    def visit(self, node: ast.ParenthesisExpr, scope: Scope):
        return self.visit(node, scope)

    @visitor.when(ast.BlockNode)
    def visit(self, node: ast.BlockNode, scope: Scope):
        last_expr = None
        for expr in node.expressions:
            last_expr = self.visit(expr, scope)

        return last_expr

    @visitor.when(ast.CaseBranchNode)
    def visit(self, node: ast.CaseBranchNode, scope: Scope):
        return self.visit(node.expr, scope)

    @visitor.when(ast.CaseNode)
    def visit(self, node: ast.CaseNode, scope: Scope):
        self.add_comment("Case of")
        ret_exp = self.visit(node.expr, scope)
        typename = self.add_local('typename')
        case_ret = self.add_local('case_ret')
        end_label = self.new_label('end')
        case_match_re_label = self.new_label('case_match_re')
        expr_void_re_label = self.new_label('expr_void_re')

        self.add_inst(cil.TypeNameNode(typename, ret_exp))

        def get_depth(x: ast.CaseBranchNode):
            return self.context.type_depth(self.context.get_type(x.type))

        # Sort cases and scopes
        sorted_cases: List[Tuple[ast.CaseBranchNode, Scope]] = []
        for case in node.cases:
            child_scope = scope.children.pop(0)
            sorted_cases.append((case, child_scope))
        sorted_cases.sort(key=lambda x: get_depth(x[0]), reverse=True)

        branch_labels = [self.new_label('case_branch') for _ in sorted_cases]

        for (case, scope), label in zip(sorted_cases, branch_labels):
            self.add_comment(f"Check for case branch {case.type}")
            cond = self.add_local('case_cond')

            self.add_inst(cil.ConformsNode(cond, ret_exp, case.type))
            self.add_inst(cil.GotoIfNode(cond, label.name))

        # Does not conform to anyone => Runtime error
        self.add_inst(cil.GotoNode(case_match_re_label.name))

        for (case, child_scope), label in zip(sorted_cases, branch_labels):
            self.add_inst(label)
            idx = self.add_local(case.id, internal=False)
            self.add_inst(cil.AssignNode(idx, ret_exp))
            branch_ret = self.visit(case, child_scope)
            self.add_inst(cil.AssignNode(case_ret, branch_ret))
            self.add_inst(cil.GotoNode(end_label.name))
    def visit(self, node: ast.ProgramNode, scope: Scope):
        # the root scope stores void to avoid semantic errors initializing
        # the void attribute of classes to void. After that every function
        # has access to void through that attribute in every class.
        # So, pop it to avoid repeated locals.
        scope.locals.pop(0)
        # build the code functions
        for class_ in node.declarations:
            tagged_scope = scope.get_tagged_scope(class_.id)

            self.visit(class_, deepcopy(tagged_scope))

            # build the entry function:
            for class_ in node.declarations:
                if class_.id == 'Main':
                    for feature in class_.features:
                        if isinstance(feature, ast.FuncDeclarationNode
                                      ) and feature.id == 'main':
                            self.add_function('main')
                            instance = self.add_local('main_instance')
                            self.add_inst(
                                cil.StaticCallNode('Main__init', instance))
                            self.add_comment('Calling main')
                            result = self.add_local('result')
                            self.add_inst(cil.ArgNode(instance))
                            self.add_inst(
                                cil.DynamicCallNode(instance, 'main', result,
                                                    None, 'Main'))
                            self.add_inst(cil.ReturnNode(0))
                            break

        # add the default functions of COOL
        # TODO: add missing instructions
        self.code += [
            cil.FunctionNode(name='Object__init',
                             params=[],
                             local_vars=[
                                 cil.LocalNode('self'),
                             ],
                             instructions=[
                                 cil.InitNode('self', 'Object'),
                                 cil.ReturnNode('self')
                             ]),
            cil.FunctionNode(
                name='Object_abort',
                params=[cil.ParamNode('self'),
                        cil.ParamNode('typename')],
                local_vars=[],
                instructions=[
                    cil.AbortNode(),
                    cil.ReturnNode(),
                ]),
            cil.FunctionNode(name='Object_type_name',
                             params=[
                                 cil.ParamNode('self'),
                             ],
                             local_vars=[
                                 cil.LocalNode('name'),
                             ],
                             instructions=[
                                 cil.TypeNameNode('name', 'self'),
                                 cil.ReturnNode('name'),
                             ]),
            cil.FunctionNode(name='Object_copy',
                             params=[
                                 cil.ParamNode('self'),
                             ],
                             local_vars=[
                                 cil.LocalNode('self_copy'),
                             ],
                             instructions=[
                                 cil.AssignNode('self_copy', 'self'),
                                 cil.ReturnNode('self_copy'),
                             ]),
            cil.FunctionNode(name='IO__init',
                             params=[],
                             local_vars=[
                                 cil.LocalNode('self'),
                             ],
                             instructions=[
                                 cil.InitNode('self', 'IO'),
                                 cil.ReturnNode('self'),
                             ]),
            cil.FunctionNode(name='IO_out_string',
                             params=[
                                 cil.ParamNode('self'),
                                 cil.ParamNode('str_addr'),
                             ],
                             local_vars=[],
                             instructions=[
                                 cil.PrintStringNode('str_addr'),
                                 cil.ReturnNode('self'),
                             ]),
            cil.FunctionNode(name='IO_out_int',
                             params=[
                                 cil.ParamNode('self'),
                                 cil.ParamNode('int_addr'),
                             ],
                             local_vars=[],
                             instructions=[
                                 cil.PrintIntNode('int_addr'),
                                 cil.ReturnNode('self'),
                             ]),
            cil.FunctionNode(name='IO_in_string',
                             params=[
                                 cil.ParamNode('self'),
                             ],
                             local_vars=[
                                 cil.LocalNode('_in_string'),
                             ],
                             instructions=[
                                 cil.ReadStringNode('_in_string'),
                                 cil.ReturnNode('_in_string'),
                             ]),
            cil.FunctionNode(name='IO_in_int',
                             params=[
                                 cil.ParamNode('self'),
                             ],
                             local_vars=[
                                 cil.LocalNode('_in_int'),
                             ],
                             instructions=[
                                 cil.ReadIntNode('_in_int'),
                                 cil.ReturnNode('_in_int'),
                             ]),
            cil.FunctionNode(name='String__init',
                             params=[],
                             local_vars=[
                                 cil.LocalNode('self'),
                             ],
                             instructions=[
                                 cil.InitNode('self', 'String'),
                                 cil.ReturnNode('self'),
                             ]),
            cil.FunctionNode(name='String_length',
                             params=[
                                 cil.ParamNode('self'),
                             ],
                             local_vars=[
                                 cil.LocalNode('_length'),
                             ],
                             instructions=[
                                 cil.LengthNode('self', '_length'),
                                 cil.ReturnNode('_length'),
                             ]),
            cil.FunctionNode(name='String_concat',
                             params=[
                                 cil.ParamNode('self'),
                                 cil.ParamNode('other_str'),
                             ],
                             local_vars=[
                                 cil.LocalNode('result'),
                             ],
                             instructions=[
                                 cil.ConcatNode('result', 'self', 'other_str'),
                                 cil.ReturnNode('result'),
                             ]),
            cil.FunctionNode(name='String_substr',
                             params=[
                                 cil.ParamNode('self'),
                                 cil.ParamNode('index'),
                                 cil.ParamNode('length'),
                             ],
                             local_vars=[
                                 cil.LocalNode('result'),
                             ],
                             instructions=[
                                 cil.SubstringNode('result', 'value', 'index',
                                                   'length'),
                                 cil.ReturnNode('result'),
                             ]),
            cil.FunctionNode(name='Bool__init',
                             params=[],
                             local_vars=[
                                 cil.LocalNode('self'),
                             ],
                             instructions=[
                                 cil.InitNode('self', 'Bool'),
                                 cil.ReturnNode('self'),
                             ]),
            cil.FunctionNode(name='Int__init',
                             params=[],
                             local_vars=[
                                 cil.LocalNode('self'),
                             ],
                             instructions=[
                                 cil.InitNode('self', 'Int'),
                                 cil.ReturnNode('self'),
                             ]),
            cil.FunctionNode(name='Void__init',
                             params=[],
                             local_vars=[
                                 cil.LocalNode('self'),
                             ],
                             instructions=[
                                 cil.InitNode('self', 'Void'),
                                 cil.ReturnNode('self'),
                             ]),
        ]
            ],
        )
    ],
)

print_int = cil.ProgramNode(
    dot_types=[],
    dot_data=[cil.DataNode("the_sum_is", '"The sum is\n"')],
    dot_code=[
        cil.FunctionNode(
            name="main",
            params=[],
            local_vars=[
                cil.LocalNode("msg"),
                cil.LocalNode("x"),
                cil.LocalNode("y"),
                cil.LocalNode("sum"),
            ],
            instructions=[
                cil.LoadNode("msg", "the_sum_is"),
                cil.AssignNode("x", 10 + 12 + 10),
                cil.AssignNode("y", "x"),
                cil.PlusNode("sum", "x", "y"),
                cil.PrintStringNode("msg"),
                cil.PrintIntNode("sum"),
                cil.ReturnNode(0),
            ],
        )
    ],
)