def visit(self, node: cil.PlusNode):
        """
        assuming left and right operands are ints
        """
        left_offset = self.get_address(node.left)
        right_offset = self.get_address(node.right)
        dest_offset = self.get_address(node.dest)

        self.add_inst(
            mips.CommentNode(f"<plus:{node.dest}<-{node.left}+{node.right}>"),
            mips.LWNode(t0,
                        (left_offset, fp)).with_comm(f"Load Int {node.left}"),
            mips.LWNode(t0, (4, t0)).with_comm('Load Int_value at offset 4'),
            mips.LWNode(
                t1, (right_offset, fp)).with_comm(f"Load Int {node.right}"),
            mips.LWNode(t1, (4, t1)).with_comm('Load Int_value at offset 4'),
            mips.ADDNode(t2, t0, t1).with_comm('Add the integer values'),
        )

        self.visit(cil.StaticCallNode('Int__init', node.dest))

        self.add_inst(
            mips.LWNode(t1, (dest_offset, fp)),
            mips.SWNode(t2, 4, t1),
            mips.CommentNode(f"</plus:{node.dest}<-{node.left}+{node.right}>"),
        )
    def visit(self, node: cil.ConformsNode):
        left_offset = self.get_address(node.left)
        # right_offset = self.get_address(node.right)
        dest_offset = self.get_address(node.dest)

        self.add_inst(
            mips.CommentNode(
                f"<conforms:{node.dest}<-{node.left}-{node.right}>"), )

        self.add_inst(
            mips.LWNode(
                t0, (left_offset, fp)).with_comm("Load left pointer to self"),
            mips.LWNode(
                a0, (0, t0)).with_comm("Load left pointer to type of self"),
            mips.LANode(a1, node.right), mips.JALNode('conforms'),
            mips.MoveNode(s0, v0))

        self.visit(cil.StaticCallNode('Bool__init', node.dest))

        self.add_inst(
            mips.LWNode(t0, (dest_offset, fp)),
            mips.SWNode(s0, 4, t0),
            mips.CommentNode(
                f"</conforms:{node.dest}<-{node.left}-{node.right}>"),
        )
    def visit(self, node: cil.SubstringNode):
        self.add_inst(
            mips.CommentNode(
                f"<substr:>{node.dest}[{node.index}:{node.length}]"))

        self.visit(cil.StaticCallNode('String__init', node.dest))

        src_address = self.get_address('self')
        dest_address = self.get_address(node.dest)
        index_address = self.get_address(node.index)
        length_address = self.get_address(node.length)

        push_src = (mips.LWNode(a0,
                                (src_address, fp)), mips.LWNode(a0, (4, a0)))
        push_length = (mips.LWNode(a2, (length_address, fp)),
                       mips.LWNode(a2, (4, a2)))
        push_index = (mips.LWNode(a1, (index_address, fp)),
                      mips.LWNode(a1, (4, a1)))

        self.add_inst(
            *push_src,
            *push_length,
            *push_index,
            mips.JALNode('substr'),
        )

        self.add_inst(
            mips.LWNode(t1, (dest_address, fp)), mips.SWNode(v0, 4, t1),
            mips.CommentNode(
                f"</substr:>{node.dest}[{node.index}:{node.length}]"))
 def visit(self, node: ast.IntegerNode, scope: Scope):
     value = self.visit(int(node.lex), scope)
     int_instance = self.add_local('int_instance')
     self.add_inst(cil.StaticCallNode('Int__init', int_instance))
     attr_index = self.root.get_type('Int').attributes.index('Int_value')
     attr_at = cil.AttributeAt('Int_value', attr_index)
     self.add_inst(cil.SetAttrNode(int_instance, attr_at, value))
     return int_instance
 def visit(self, node: ast.BooleanNode, scope: Scope):
     value = self.visit(node.lex == 'true', scope)
     bool_instance = self.add_local('bool_instance')
     self.add_inst(cil.StaticCallNode('Bool__init', bool_instance))
     attr_index = self.root.get_type('Bool').attributes.index('Bool_value')
     attr_at = cil.AttributeAt('Bool_Value', attr_index)
     self.add_inst(cil.SetAttrNode(bool_instance, attr_at, value))
     return bool_instance
    def visit(self, node: cil.ReadIntNode):
        address = self.get_address(node.dest)

        self.add_inst(mips.CommentNode(f"<readint:{node.dest}>"),
                      mips.LINode(v0, 5), mips.SysCallNode(),
                      mips.MoveNode(t2, v0))

        self.visit(cil.StaticCallNode("Int__init", node.dest))

        self.add_inst(mips.LWNode(t1, (address, fp)), mips.SWNode(t2, 4, t1),
                      mips.LWNode(v0, (address, fp)),
                      mips.CommentNode(f"</readint:{node.dest}>"))
    def visit(self, node: ast.StringNode, scope: Scope):
        self.add_comment('Instantiating string: ' +
                         (node.lex if len(node.lex) < 20 else node.lex[:15] +
                          '...'))

        value = self.visit(node.lex, scope)
        str_instance = self.add_local('str_instance')
        self.add_inst(cil.StaticCallNode('String__init', str_instance))
        attr_index = self.root.get_type('String').attributes.index(
            'String_value')
        attr_at = cil.AttributeAt('String_value', attr_index)
        self.add_inst(cil.SetAttrNode(str_instance, attr_at, value))
        return str_instance
    def visit(self, node: cil.NegationNode):
        src_offset = self.get_address(node.src)
        dest_offset = self.get_address(node.dest)

        self.add_inst(mips.CommentNode(f"<negate:{node.dest}<-~{node.src}>"),
                      mips.LWNode(t0, (src_offset, fp)),
                      mips.LWNode(t0, (4, t0)), mips.XORINode(t1, t0, 1))

        self.visit(cil.StaticCallNode('Bool__init', node.dest))

        self.add_inst(
            mips.LWNode(t0, (dest_offset, fp)),
            mips.SWNode(t1, 4, t0),
            mips.CommentNode(f"</negate:{node.dest}<-~{node.src}>"),
        )
    def visit(self, node: cil.IsVoidNode):
        src = self.get_address(node.src)
        dest = self.get_address(node.dest)

        self.add_inst(mips.CommentNode(f"<isvoid:{node.dest}-{node.src}>"), )

        self.visit(cil.StaticCallNode('Bool__init', node.dest))

        self.add_inst(
            mips.LWNode(a0, (src, fp)).with_comm('Push instance pointer'),
            mips.JALNode('isvoid'),
            mips.LWNode(t0, (dest, fp)).with_comm('Load Bool pointer'),
            mips.SWNode(v0, 4,
                        t0).with_comm('Save isvoid result as value of Bool'),
            mips.CommentNode(f"</isvoid:{node.dest}-{node.src}>"),
        )
    def visit(self, node: cil.ComplementNode):
        src = self.get_address(node.src)
        dest = self.get_address(node.dest)

        self.add_inst(mips.CommentNode(f"</complement:{node.dest}>"), )

        self.visit(cil.StaticCallNode('Int__init', node.dest))

        self.add_inst(
            mips.LWNode(t0, (src, fp)),
            mips.LWNode(t0, (4, t0)),
            mips.NOTNode(t0, t0),
            mips.ADDINode(t0, t0, 1),
            mips.LWNode(t1, (dest, fp)),
            mips.SWNode(t0, 4, t1),
            mips.CommentNode(f"</complement:{node.dest}>"),
        )
    def visit(self, node: cil.TypeNameNode):
        name_offset = self.types['Object'].name_offset
        src_offset = self.get_address(node.src)

        self.add_inst(mips.CommentNode(f"<typename:{node.dest}-{node.src}>"), )

        self.visit(cil.StaticCallNode('String__init', node.dest))

        self.add_inst(
            mips.LWNode(t0,
                        (src_offset, fp)).with_comm('Load pointer to self'),
            mips.LWNode(t0, (0, t0)).with_comm('Load pointer to type of self'),
            mips.ADDINode(t0, t0,
                          name_offset).with_comm('Point to name of type'),
            mips.SWNode(
                t0, 4,
                v0).with_comm('Save name of the type in the new string'),
            mips.CommentNode(f"</typename:{node.dest}-{node.src}>"),
        )
    def visit(self, node: cil.LengthNode):
        self.add_inst(
            mips.CommentNode(f"<length:{node.dest}=len({node.src})>"))

        self.visit(cil.StaticCallNode("Int__init", node.dest))

        src_address = self.get_address(node.src)
        dest_address = self.get_address(node.dest)

        self.add_inst(
            mips.LWNode(a0, (src_address, fp)),
            mips.LWNode(a0, (4, a0)),
            mips.JALNode('length'),
            mips.LWNode(t1, (dest_address, fp)),
            mips.SWNode(v0, 4, t1),
        )

        self.add_inst(
            mips.CommentNode(f"</length:{node.dest}=len({node.src})>"))
    def visit(self, node: cil.ReadStringNode):
        self.add_inst(mips.CommentNode(f"<readstring:{node.dest}>"))

        address = self.get_address(node.dest)

        self.add_inst(mips.LINode(a0, 512), mips.JALNode('malloc'),
                      mips.MoveNode(t2, v0))
        self.add_inst(
            mips.MoveNode(a0, t2),
            mips.LINode(a1, 512),
            mips.LINode(v0, 8),
            mips.SysCallNode(),
        )
        self.visit(cil.StaticCallNode('String__init', node.dest))
        self.add_inst(mips.LWNode(t0, (address, fp)), mips.SWNode(t2, 4, t0))

        # Remove eol
        self.add_inst(mips.MoveNode(a0, t2), mips.JALNode('remove_eol'))

        self.add_inst(mips.CommentNode(f"</readstring:{node.dest}>"))
    def visit(self, node: cil.EqualNode):
        left = self.get_address(node.left)
        right = self.get_address(node.right)
        dest = self.get_address(node.dest)

        self.add_inst(
            mips.CommentNode(
                f"<equal: {node.dest} <- {node.left} = {node.right}>"), )

        self.visit(cil.StaticCallNode("Bool__init", node.dest))

        self.add_inst(
            mips.LWNode(a0, (left, fp)),
            mips.LWNode(a1, (right, fp)),
            mips.JALNode("equal"),
            mips.LWNode(t0, (dest, fp)),
            mips.SWNode(v0, 4, t0),
            mips.CommentNode(
                f"<equal: {node.dest} <- {node.left} = {node.right}>"),
        )
    def visit(self, node: cil.StarNode):
        left_offset = self.get_address(node.left)
        right_offset = self.get_address(node.right)
        dest_offset = self.get_address(node.dest)

        self.add_inst(
            mips.CommentNode(f"<star:{node.dest}<-{node.left}-{node.right}>"),
            mips.LWNode(t0, (left_offset, fp)),  # load Int_value at offset 4
            mips.LWNode(t0, (4, t0)),
            mips.LWNode(t1, (right_offset, fp)),  # load Int_value at offset 4
            mips.LWNode(t1, (4, t1)),
            mips.MULTNode(t2, t0, t1),  # multiply the integer values
        )

        self.visit(cil.StaticCallNode('Int__init', node.dest))

        self.add_inst(
            mips.LWNode(t1, (dest_offset, fp)),
            mips.SWNode(t2, 4, t1),
            mips.CommentNode(f"</star:{node.dest}<-{node.left}-{node.right}>"),
        )
    def visit(self, node: cil.ConcatNode):
        self.add_inst(
            mips.CommentNode(f"<concat:{node.dest}={node.str1}+{node.str2}>"))

        self.visit(cil.StaticCallNode("String__init", node.dest))

        str1_address = self.get_address(node.str1)
        str2_address = self.get_address(node.str2)
        dest_address = self.get_address(node.dest)

        # Calc length of str1 and save it in t0
        length_of_str1 = (mips.LWNode(a0, (str1_address, fp)),
                          mips.LWNode(a0, (4, a0)), mips.JALNode('length'),
                          mips.MoveNode(t0, v0))
        # Calc length of str2 and save it in t1
        length_of_str2 = (mips.LWNode(a0, (str2_address, fp)),
                          mips.LWNode(a0, (4, a0)), mips.JALNode('length'),
                          mips.MoveNode(t1, v0))

        push_str1 = (mips.LWNode(a0,
                                 (str1_address, fp)), mips.LWNode(a0, (4, a0)))
        push_str2 = (mips.LWNode(a1,
                                 (str2_address, fp)), mips.LWNode(a1, (4, a1)))
        push_length = (mips.ADDNode(a2, t0, t1), )

        self.add_inst(
            *length_of_str1,
            *length_of_str2,
            *push_str1,
            *push_str2,
            *push_length,
            mips.JALNode('concat'),
        )

        self.add_inst(mips.LWNode(t1, (dest_address, fp)),
                      mips.SWNode(v0, 4, t1))

        self.add_inst(
            mips.CommentNode(f"</concat:{node.dest}={node.str1}+{node.str2}>"))
    def visit(self, node: ast.InstantiateNode, scope: Scope):
        if node.lex == 'String':
            return self.visit(ast.StringNode('""'), scope)
        elif node.lex == 'Bool':
            return self.visit(ast.BooleanNode('false'), scope)
        elif node.lex == 'Int':
            return self.visit(ast.IntegerNode('0'), scope)

        self.add_comment(f'Instantiating type {node.lex}')

        # type_node = self.root.get_type(node.lex)
        # attr_values = []
        # for attr in type_node.attributes:
        #     attr_expr = type_node.get_attr_node(attr)
        #     attr_values.append(self.visit(attr_expr, scope))
        instance = self.add_local(f'inst_of_{node.lex}')
        self.add_inst(cil.StaticCallNode(f'{node.lex}__init', instance))
        # for attr, attr_value in zip(type_node.attributes, attr_values):
        #     attr_index = type_node.attributes.index(attr)
        #     attr_at = cil.AttributeAt(attr, attr_index)
        #     self.add_inst(
        #         cil.SetAttrNode(instance, attr_at, attr_value)
        #     )
        return instance
    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'),
                             ]),
        ]