def visit(self, node):
        self.context = Context()
        self.context.types["Object"] = ObjectType()
        self.context.types["Int"] = IntType()
        self.context.types["String"] = StringType()
        self.context.types["Bool"] = BoolType()
        self.context.types["AUTO_TYPE"] = AutoType()
        self.context.types["SELF_TYPE"] = SelfType()
        self.context.types["IO"] = IOType()

        object_type = self.context.get_type("Object")
        for typex in self.context.types.values():
            if typex == object_type:
                continue
            typex.set_parent(object_type)

        for declaration in node.declarations:
            self.visit(declaration)

        copy_visitor = CopyVisitor()
        newAst = copy_visitor.visit(node)
        newAst.context = self.context

        # Reset state
        self.context = None
        self.errors = None

        return newAst
class TypeCollector(object):
    def __init__(self, errors=[]):
        self.context = None
        self.errors = errors

    @visitor.on('node')
    def visit(self, node):
        pass

    @visitor.when(ProgramNode)
    def visit(self, node):
        self.context = Context()

        parent_type = self.context.create_type('Object')
        child_types = [
            self.context.create_type('String'),
            self.context.create_type('Int'),
            self.context.create_type('Bool'),
            self.context.create_type('IO'),
            self.context.create_type('void'),
            self.context.create_type('SELF_TYPE'),
            self.context.create_type('AUTO_TYPE')
        ]
        for child in child_types:
            child.set_parent(parent_type)

        for declaration in node.declarations:
            self.visit(declaration)

    @visitor.when(ClassDeclarationNode)
    def visit(self, node):
        try:
            self.context.create_type(node.id)
        except SemanticError as error:
            self.errors.append(error.text)
    def visit(self, node):
        self.context = Context()
        self.init_default_classes()

        for class_def in node.declarations:
            self.visit(class_def)

        ordered = []
        self.check_type_tree(ordered, self.class_tree)
        node.declarations = ordered
        self.context.type_tree = self.class_tree
        self.context.type_tree["<error>"] = tuple()
    def visit(self, node):
        self.context = Context()
        self.define_built_in_types()

        # Adding built-in types to context
        for typex in built_in_types:
            self.context.types[typex.name] = typex

        for declaration in node.declarations:
            self.visit(declaration)

        self.check_parents()
        self.check_cyclic_inheritance()

        # Order class declarations according to their depth in the inheritance tree
        node.declarations = self.order_types(node)
class TypeCollector(object):
    def __init__(self, errors=[]):
        self.context = None
        self.errors = errors

    @visitor.on("node")
    def visit(self, node):
        pass

    @visitor.when(ProgramNode)
    def visit(self, node):
        self.context = Context()
        self.context.types["Object"] = ObjectType()
        self.context.types["Int"] = IntType()
        self.context.types["String"] = StringType()
        self.context.types["Bool"] = BoolType()
        self.context.types["AUTO_TYPE"] = AutoType()
        self.context.types["SELF_TYPE"] = SelfType()
        self.context.types["IO"] = IOType()

        object_type = self.context.get_type("Object")
        for typex in self.context.types.values():
            if typex == object_type:
                continue
            typex.set_parent(object_type)

        for declaration in node.declarations:
            self.visit(declaration)

        copy_visitor = CopyVisitor()
        newAst = copy_visitor.visit(node)
        newAst.context = self.context

        # Reset state
        self.context = None
        self.errors = None

        return newAst

    @visitor.when(ClassDeclarationNode)
    def visit(self, node):
        try:
            self.context.create_type(node.id.lex)
        except SError as error:  # class alerady defined
            node_row, node_col = node.id.location
            self.errors.append(SemanticError(node_row, node_col, error.text))
    def visit(self, node):
        self.context = Context()

        parent_type = self.context.create_type('Object')
        child_types = [
            self.context.create_type('String'),
            self.context.create_type('Int'),
            self.context.create_type('Bool'),
            self.context.create_type('IO'),
            self.context.create_type('void'),
            self.context.create_type('SELF_TYPE'),
            self.context.create_type('AUTO_TYPE')
        ]
        for child in child_types:
            child.set_parent(parent_type)

        for declaration in node.declarations:
            self.visit(declaration)
class TypeCollector(object):
    def __init__(self, errors=[]):
        self.context = None
        self.errors = errors
        self.parent = {}

    @visitor.on('node')
    def visit(self, node):
        pass

    @visitor.when(ProgramNode)
    def visit(self, node):
        self.context = Context()
        self.define_built_in_types()

        # Adding built-in types to context
        for typex in built_in_types:
            self.context.types[typex.name] = typex

        for declaration in node.declarations:
            self.visit(declaration)

        self.check_parents()
        self.check_cyclic_inheritance()

        # Order class declarations according to their depth in the inheritance tree
        node.declarations = self.order_types(node)

    @visitor.when(ClassDeclarationNode)
    def visit(self, node):
        # flag will be True if the class is succesfully added to the context
        flag = False
        try:
            if node.id == 'AUTO_TYPE':
                raise SemanticError('Name of class can\'t be autotype')
            self.context.create_type(node.id)
            flag = True
            self.parent[node.id] = node.parent
        except SemanticError as ex:
            self.errors.append(ex.text)

        # changing class id so it can be added to context
        while not flag:
            node.id = f'1{node.id}'
            try:
                self.context.create_type(node.id)
                flag = True
                self.parent[node.id] = node.parent
            except SemanticError:
                pass

    def define_built_in_types(self):
        objectx = ObjectType()
        iox = IOType()
        intx = IntType()
        stringx = StringType()
        boolx = BoolType()
        self_type = SelfType()
        autotype = AutoType()

        # Object Methods
        objectx.define_method('abort', [], [], objectx, [])
        objectx.define_method('type_name', [], [], stringx, [])
        objectx.define_method('copy', [], [], self_type, [])

        # IO Methods
        iox.define_method('out_string', ['x'], [stringx], self_type, [None])
        iox.define_method('out_int', ['x'], [intx], self_type, [None])
        iox.define_method('in_string', [], [], stringx, [])
        iox.define_method('in_int', [], [], intx, [])

        # String Methods
        stringx.define_method('length', [], [], intx, [])
        stringx.define_method('concat', ['s'], [stringx], stringx, [None])
        stringx.define_method('substr', ['i', 'l'], [intx, intx], stringx,
                              [None])

        # Setting Object as parent
        iox.set_parent(objectx)
        stringx.set_parent(objectx)
        intx.set_parent(objectx)
        boolx.set_parent(objectx)

        built_in_types.extend(
            [objectx, iox, stringx, intx, boolx, self_type, autotype])

    def check_parents(self):
        for item in self.parent.keys():
            item_type = self.context.get_type(item)
            if self.parent[item] is None:
                # setting Object as parent
                item_type.set_parent(built_in_types[0])
            else:
                try:
                    typex = self.context.get_type(self.parent[item])
                    if not typex.can_be_inherited():
                        self.errors.append(
                            f'Class {item} can not inherit from {typex.name}')
                        typex = built_in_types[0]
                    item_type.set_parent(typex)
                except SemanticError as ex:
                    self.errors.append(ex.text)
                    item_type.set_parent(built_in_types[0])

    def check_cyclic_inheritance(self):
        flag = []

        def find(item):
            for i, typex in enumerate(flag):
                if typex.name == item.name:
                    return i
            return len(flag)

        def check_path(idx, item):
            while True:
                flag.append(item)
                parent = item.parent
                if parent is None:
                    break
                pos = find(parent)
                if pos < len(flag):
                    if pos >= idx:
                        self.errors.append(
                            f'Class {item.name} can not inherit from {parent.name}'
                        )
                        item.parent = built_in_types[0]
                    break
                item = parent

        for item in self.context.types.values():
            idx = find(item)
            if idx == len(flag):
                check_path(idx, item)

    def order_types(self, node):
        sorted_declarations = []
        flag = [False] * len(node.declarations)

        change = True
        while change:
            change = False

            current = []
            for i, dec in enumerate(node.declarations):
                if not flag[i]:
                    typex = self.context.get_type(dec.id)
                    if typex.parent.name in [
                            item.id for item in sorted_declarations
                    ] or any(typex.parent.name == bit.name
                             for bit in built_in_types):
                        current.append(dec)
                        flag[i] = True
                        change = True

            sorted_declarations.extend(current)

        return sorted_declarations
class TypeCollector(object):
    def __init__(self):
        self.context = None
        self.errors = []

        self.class_tree = {
            "Object": ["IO", "String", "Int", "Bool"],
            "IO": [],
            "String": [],
            "Int": [],
            "Bool": []
        }
        self.types_nodes = dict()

    @visitor.on('node')
    def visit(self, node):
        pass

    @visitor.when(ProgramNode)
    def visit(self, node):
        self.context = Context()
        self.init_default_classes()

        for class_def in node.declarations:
            self.visit(class_def)

        ordered = []
        self.check_type_tree(ordered, self.class_tree)
        node.declarations = ordered
        self.context.type_tree = self.class_tree
        self.context.type_tree["<error>"] = tuple()

    @visitor.when(ClassDeclarationNode)
    def visit(self, node):
        try:
            self.context.create_type(node.id)
            if node.id[0] != node.id[0].upper():
                self.errors.append(
                    f"Class {node.id} must start with a capital word.")
            self.types_nodes[node.id] = node
            if not node.id in self.class_tree:
                self.class_tree[node.id] = []
            if node.parent:
                if node.parent in {"String", "Int", "Bool"}:
                    raise SemanticError(
                        f"Class {node.id} cannot inherit from {node.parent} beacuse is forbidden."
                    )

                try:
                    self.class_tree[node.parent].append(node.id)
                except KeyError:
                    self.class_tree[node.parent] = [node.id]
            else:
                node.parent = "Object"
                self.class_tree["Object"].append(node.id)

        except SemanticError as err:
            self.errors.append(err.text)

    def init_default_classes(self):
        self.context.create_type('Object').index = 0
        self.context.create_type('String')
        self.context.create_type('Int')
        self.context.create_type('IO')
        self.context.create_type('Bool')

    def check_type_tree(self, ordered, graph):
        visited = set(["Object"])
        self.get_type_hierarchy("Object", graph, visited, ordered, 1)

        for node in graph:
            if not node in visited:
                visited.add(node)
                self.errors.append(
                    "Circular Heritage: " +
                    str(get_circular_heritage(node, graph, [node], visited)))

    def get_type_hierarchy(self, root, graph, visited: set, ordered: list,
                           index):
        if not root in graph:
            return None

        for node in graph[root]:
            if not node in visited:
                visited.add(node)
                if node not in {"Int", "String", "IO", "Bool", "Object"}:
                    ordered.append(self.types_nodes[node])
                self.context.get_type(node).index = index
                self.get_type_hierarchy(node, graph, visited, ordered,
                                        index + 1)