Beispiel #1
0
class Semant(object):
    """ Analyzes semantically the code. """

    def __init__(self, ast):
        self.ast = ast
        self.classes = {}
        self.parents = defaultdict(set)
        self.scope = Scope()

    def build(self):
        self.__create_default_classes()
        self.__create_symbol_tables()
        self.__check_undefined_classes()
        self.__check_inheritance_cycles()
        self.__check_inheritence_and_add_methods_in_children()

        for _class in self.classes.keys():
            self.__check_scope_and_type(self.classes[_class])

    def __create_default_classes(self):
        # Object there is no parent
        objc = Class("Object", None, [
            Method('abort', [], 'Object', None),
            Method('type_name', [], 'String', None),
            Method('copy', [], 'SELF_TYPE', None),
        ])
        # IO inherits from Object
        ioc = Class("IO", "Object", [
            Method('out_string', [('arg', 'String')], 'SELF_TYPE', None),
            Method('out_int', [('arg', 'Int')], 'SELF_TYPE', None),
            Method('in_string', [], 'String', None),
            Method('in_int', [], 'Int', None),
        ])
        # Interge inherits from Object
        intc = Class("Int", "Object", [
            Attr('variable', 'Int', Int(content=0))
        ])
        # String inherits from Object
        stringc = Class("String", "Object", [
            Method('length', [], 'Int', None),
            Method('concat', [('arg', 'String')], 'String', None),
            Method(
                'substr', [('arg1', 'Int'), ('arg2', 'Int')], 'String', None
            ),
        ])
        # Boolean inherits from Object
        boolc = Class("Bool", "Object", [
            Attr('variable', 'Bool', Bool(content=False))
        ])

        self.ast += [objc, ioc, intc, stringc, boolc]

    def __create_symbol_tables(self):
        """
            Create two tables:
            One with all the classes and another with all
            parents classes.
        """
        for _class in self.ast:
            if _class.name in self.classes:
                raise ClassAlreadyDefinedError(_class.name)

            else:
                self.classes[_class.name] = _class

            if _class.name != 'Object':
                self.parents[_class.parent].add(_class.name)

    def __check_undefined_classes(self):
        """
            Check if every parent is defined in classes table. (self.classes)
        """
        parents = self.parents.keys()

        for parent in parents:
            if parent not in self.classes:
                class_name = self.parents[parent]
                raise UndefinedParentError(class_name, parent)

    def __check_inheritance_cycles(self):
        """
            Check if every class has your right parent and children.

            First, set every class(including parents) as False.
            Then, visit each class and their children.
        """
        visited = {}
        for parent_name in self.parents.keys():
            visited[parent_name] = False
            for child_name in self.parents[parent_name]:
                visited[child_name] = False

        # Visit every class, recursively, starting with Object
        self.__visit_tree('Object', visited)

        # If some class has False value, means that the visitor
        # couldn't get in that class. So, some class is missing.
        for key, value in visited.items():
            if not value:
                raise InheritanceError(key)

    def __visit_tree(self, _class, visited):
        visited[_class] = True

        # If a _class is not in parents,
        # it is not a parent, so, don't have children. Get out.
        if _class not in self.parents.keys():
            return

        for child in self.parents[_class]:
            self.__visit_tree(child, visited)

    def __check_inheritence_and_add_methods_in_children(self, _class='Object'):
        """
            Check attributes and methods from inheritance and
            if it is ok, add them from parent to child.
        """
        cl = self.classes[_class]

        if cl.parent:
            _class_parent = self.classes[cl.parent]

            attrs_of_parent = self.__get_attributes(_class_parent)
            attrs_of_child = self.__get_attributes(cl)

            self.__check_same_attribute(
                attrs_of_parent, attrs_of_child, _class
            )

            methods_of_parent = self.__get_methods(_class_parent)
            methods_of_child = self.__get_methods(cl)

            method_signatures_of_parent = self.__get_signatures(
                methods_of_parent
            )
            method_signatures_of_child = self.__get_signatures(
                methods_of_child
            )

            self.__check_same_signature(
                methods_of_child,
                method_signatures_of_parent,
                method_signatures_of_child
            )

            self.__add_method_from_parent_to_child(
                cl, methods_of_parent, methods_of_child
            )

            self.__add_attr_from_parent_to_child(cl, attrs_of_parent)

        # Goes recursively to all children
        all_children = self.parents[_class]
        for child in all_children:
            self.__check_inheritence_and_add_methods_in_children(child)

    def __get_attributes(self, _class):
        return [i for i in _class.feature_list if isAttribute(i)]

    def __check_same_attribute(self, parent, child, _class):
        """
            It's illegal to redefine attribute names in child class.
        """
        for p_attr in parent:
            for c_attr in child:
                if p_attr.name == c_attr.name:
                    raise RedefinedAttributeError(_class)

    def __get_methods(self, _class):
        return [i for i in _class.feature_list if isMethod(i)]

    def __get_signatures(self, methods):
        method_signatures = {}
        for method in methods:
            method_signatures[method.name] = {}
            for formal in method.formal_list:
                # formal is a tuple that has 2 values:
                # The first one is the name of the argument;
                # The second, is the type of the argument (Int, Bool,...)
                method_signatures[method.name][formal[0]] = formal[1]
            method_signatures[method.name]['return'] = method.return_type
        return method_signatures

    def __check_same_signature(self, child_methods, sig_parent, sig_child):
        """
            If a class "B" inherits a method "m" from an ancestor class "A",
            then "B" may override the inherited definition of "m" provided
            the number of arguments, the types of the formal parameters,
            and the return type are exactly the same in both definitions.
        """
        for method in child_methods:
            if method.name in sig_parent:
                parent_signature = sig_parent[method.name]
                child_signature = sig_child[method.name]

                if parent_signature != child_signature:
                    raise RedefinedMethodError(method.name)

    def __add_method_from_parent_to_child(self, _cl, p_methods, c_methods):
        for method in p_methods:
            if method.name not in c_methods:
                copied_method = deepcopy(method)
                # Add at the beginning
                _cl.feature_list.insert(0, copied_method)

    def __add_attr_from_parent_to_child(self, _cl, attrs_of_parent):
        for attr in attrs_of_parent:
            # Add at the beginning
            _cl.feature_list.insert(0, deepcopy(attr))

    def __check_scope_and_type(self, _class):
        """
            Check scope and type for each class.

            If it's a method, goes recursively inside the body.

            When a scope is created?
            With a new block, let, case,

            OBS: Every attribute is protected and every method is public.
        """

        for feature in _class.feature_list:
            _type = returned_type(feature, _class)

            if isAttribute(feature):
                value_type = get_expression_type(
                    feature.body, _class, self.scope
                )

                if not value_type:
                    # If value_type is None, means that feature.body is
                    # a complex expression, need to be checked.
                    self.__check_children(feature.body, _class)

                # Test if the attribute value type is the same as declared.
                if feature.type != value_type:
                    raise AttributeTypeError(feature, value_type)

                self.scope.add(feature.name, _type)

            elif isMethod(feature):
                self.scope.add(feature.name, _type)

                # Add arguments to scope. name:type
                for formal in feature.formal_list:
                    self.scope.add(formal[0], formal[1])

                self.__check_children(feature.body, _class)

    def __check_children(self, expression, _class):
        if isinstance(expression, Block):
            self.scope.new()

            for expr in expression.body:
                self.__check_children(expr, _class)

            self.scope.destroy()

        elif isinstance(expression, Dispatch):
            self.__check_children(expression.body, _class)

            # Get return type
            if expression.body == 'self':
                _class_name = _class.name
            else:
                try:
                    _class_name = expression.body.return_type
                except AttributeError:
                    # If the expression is an Object and there is no
                    # return_type variable, so, need get the type.
                    _class_name = get_expression_type(
                        expression.body, _class, self.scope
                    )

            # Get the whole class' structure
            _class_content = self.classes[_class_name]

            called_method = False

            # Parse the structure untill match the method name
            for feature in _class_content.feature_list:
                if isMethod(feature) and feature.name == expression.method:
                    called_method = True

                    if len(feature.formal_list) != len(expression.expr_list):
                        raise NumberOfArgumentError(feature.name, _class_name)

                    formals = zip(
                        feature.formal_list, expression.expr_list,
                    )

                    # Test if the arguments types are not equals
                    for feat, called in formals:
                        expression_type = get_expression_type(
                            called, _class, self.scope
                        )
                        # feat[0] is the name and feat[1] the type
                        if feat[1] != expression_type:
                            raise ArgumentTypeError(feature, _class_name)

                    # For default, the method returns the host class. SELF_TYPE
                    last_type = _class_name

                    feature_type = returned_type(feature, _class)

                    # If exists a body, means that exists one or more
                    # expressions inside the method.
                    if feature.body:
                        try:
                            # If have a Block, must look the last expression,
                            # because it is the type that will be returned.
                            last_expression = feature.body.body[-1]
                        except AttributeError:
                            last_expression = feature.body

                        last_type = get_expression_type(
                            last_expression, _class, self.scope
                        )

                    # If the returns types are not equals, raise an error
                    if feature_type != last_type:
                        raise ReturnedTypeError(
                            feature.name, _class_name, feature_type, last_type
                        )

            # If didn't match the method name...
            if not called_method:
                raise UndefinedMethodError(expression.method, _class_name)

        elif isinstance(expression, Let):
            self.scope.new()
            self.scope.add(expression.object, expression.type)

            # Test if the declared type is the same type as
            # the given value
            value_type = get_expression_type(
                expression.init, _class, self.scope
            )
            if expression.type != value_type:
                raise DeclaredTypeError(expression.type, value_type)

            self.__check_children(expression.body, _class)

            self.scope.destroy()

        elif isinstance(expression, While):
            self.__check_children(expression.predicate, _class)
            self.__check_children(expression.body, _class)
            # If the methods above did not raise an error, means that
            # the body's type is Int or an Object.
            # If is an Object and the root type is not a Bool,
            # must raise an error.
            self.__raise_if_not_bool(expression, _class, 'While')

        elif isinstance(expression, Lt) or isinstance(expression, Le):
            first_type, second_type = self.__get_params_types(
                expression, _class
            )

            if first_type != 'Int' or second_type != 'Int':
                raise TypeCheckError(first_type, second_type, _class)

        elif isinstance(expression, Eq):
            """
                The comparison = is a special case.
                If either <expr1> or <expr2> has static type Int, Bool,
                or String, then the other must have the same static type.
            """
            first_type, second_type = self.__get_params_types(
                expression, _class
            )
            types = ['String', 'Int', 'Bool']
            if first_type not in types or second_type not in types:
                raise EqualTypeCheckError(first_type, second_type, _class)

            if first_type != second_type:
                raise EqualCheckError(first_type, second_type, _class)

        elif any(isinstance(expression, X) for X in [Plus, Sub, Mult, Div]):
            """
                The static types of the two sub-expressions must be Int.

                Cool has only integer division.
            """
            first_type, second_type = self.__get_params_types(
                expression, _class
            )

            if first_type != 'Int' or second_type != 'Int':
                raise ArithmeticError(first_type, second_type, _class)

        elif isinstance(expression, Assign):
            self.__check_children(expression.body, _class)
            # If the method above did not raise an error, means that
            # the body type is Int. Just need to test name type now.
            name_type = get_expression_type(
                expression.name, _class, self.scope
            )

            if name_type != 'Int':
                raise AssignError(name_type, 'Int', _class)

        elif isinstance(expression, If):
            self.__check_children(expression.predicate, _class)
            self.__check_children(expression.then_body, _class)
            self.__check_children(expression.else_body, _class)
            # If the methods above did not raise an error, means that
            # the body type is Int or an Object.
            # If is an Object and the root type is not a Bool,
            # must raise an error.
            self.__raise_if_not_bool(expression, _class, 'If')

    def __raise_if_not_bool(self, expression, _class, statement):
        if isinstance(expression.predicate, Object):
                obj_type = get_expression_type(
                    expression.predicate, _class, self.scope
                )
                if obj_type != 'Bool':
                    raise ConditionStatementError(statement, obj_type, _class)

    def __get_params_types(self, expression, _class):
        first_type = get_expression_type(
            expression.first, _class, self.scope
        )
        second_type = get_expression_type(
            expression.second, _class, self.scope
        )
        return first_type, second_type
Beispiel #2
0
class Semant(object):
    """ Analyzes semantically the code. """
    def __init__(self, ast):
        self.ast = ast
        self.classes = {}
        self.parents = defaultdict(set)
        self.scope = Scope()

    def build(self):
        self.__create_default_classes()
        self.__create_symbol_tables()
        self.__check_undefined_classes()
        self.__check_inheritance_cycles()
        self.__check_inheritence_and_add_methods_in_children()

        for _class in self.classes.keys():
            self.__check_scope_and_type(self.classes[_class])

    def __create_default_classes(self):
        # Object there is no parent
        objc = Class("Object", None, [
            Method('abort', [], 'Object', None),
            Method('type_name', [], 'String', None),
            Method('copy', [], 'SELF_TYPE', None),
        ])
        # IO inherits from Object
        ioc = Class("IO", "Object", [
            Method('out_string', [('arg', 'String')], 'SELF_TYPE', None),
            Method('out_int', [('arg', 'Int')], 'SELF_TYPE', None),
            Method('in_string', [], 'String', None),
            Method('in_int', [], 'Int', None),
        ])
        # String inherits from Object
        stringc = Class("String", "Object", [
            Method('length', [], 'Int', None),
            Method('concat', [('arg', 'String')], 'String', None),
            Method('substr', [('arg1', 'Int'),
                              ('arg2', 'Int')], 'String', None),
        ])

        self.ast += [objc, ioc, stringc]

    def __create_symbol_tables(self):
        """
            Create two tables:
            One with all the classes and another with all
            parents classes.
        """
        for _class in self.ast:
            if _class.name in self.classes:
                raise ClassAlreadyDefinedError(_class.name)

            else:
                self.classes[_class.name] = _class

            if _class.name != 'Object':
                self.parents[_class.parent].add(_class.name)

    def __check_undefined_classes(self):
        """
            Check if every parent is defined in classes table. (self.classes)
        """
        parents = self.parents.keys()

        for parent in parents:
            if parent not in self.classes:
                class_name = self.parents[parent]
                raise UndefinedParentError(class_name, parent)

    def __check_inheritance_cycles(self):
        """
            Check if every class has your right parent and children.

            First, set every class(including parents) as False.
            Then, visit each class and their childs.
        """
        visited = {}
        for parent_name in self.parents.keys():
            visited[parent_name] = False
            for child_name in self.parents[parent_name]:
                visited[child_name] = False

        # Visit every class, recursively, starting with Object
        self.__visit_tree('Object', visited)

        # If some class has False value, means that the visitor
        # couldn't get in that class. So, some class is missing.
        for key, value in visited.items():
            if not value:
                raise InheritanceError(key)

    def __visit_tree(self, _class, visited):
        visited[_class] = True

        # If a _class is not in parents,
        # it is not a parent, so, don't have children. Get out.
        if _class not in self.parents.keys():
            return

        for child in self.parents[_class]:
            self.__visit_tree(child, visited)

    def __check_inheritence_and_add_methods_in_children(self, _class='Object'):
        """
            Check attributes and methods from inheritance and
            if it is ok, add them from parent to child.
        """
        cl = self.classes[_class]

        if cl.parent:
            _class_parent = self.classes[cl.parent]

            attrs_of_parent = self.__get_attributes(_class_parent)
            attrs_of_child = self.__get_attributes(cl)

            self.__check_same_attribute(attrs_of_parent, attrs_of_child,
                                        _class)

            methods_of_parent = self.__get_methods(_class_parent)
            methods_of_child = self.__get_methods(cl)

            method_signatures_of_parent = self.__get_signatures(
                methods_of_parent)
            method_signatures_of_child = self.__get_signatures(
                methods_of_child)

            self.__check_same_signature(methods_of_child,
                                        method_signatures_of_parent,
                                        method_signatures_of_child)

            self.__add_method_from_parent_to_child(cl, methods_of_parent,
                                                   methods_of_child)

            self.__add_attr_from_parent_to_child(cl, attrs_of_parent)

        # Goes recursively to all children
        all_children = self.parents[_class]
        for child in all_children:
            self.__check_inheritence_and_add_methods_in_children(child)

    def __get_attributes(self, _class):
        return [i for i in _class.feature_list if isAttribute(i)]

    def __check_same_attribute(self, parent, child, _class):
        """
            It's illegal to redefine attribute names in child class.
        """
        for p_attr in parent:
            for c_attr in child:
                if p_attr.name == c_attr.name:
                    raise RedefinedAttributeError(_class)

    def __get_methods(self, _class):
        return [i for i in _class.feature_list if isMethod(i)]

    def __get_signatures(self, methods):
        method_signatures = {}
        for method in methods:
            method_signatures[method.name] = {}
            for formal in method.formal_list:
                # formal is a tuple that has 2 values:
                # The first one is the name of the argument;
                # The second, is the type of the argument (Int, Bool,...)
                method_signatures[method.name][formal[0]] = formal[1]
            method_signatures[method.name]['return'] = method.return_type
        return method_signatures

    def __check_same_signature(self, child_methods, sig_parent, sig_child):
        """
            If a class "B" inherits a method "m" from an ancestor class "A",
            then "B" may override the inherited definition of "m" provided
            the number of arguments, the types of the formal parameters,
            and the return type are exactly the same in both definitions.
        """
        for method in child_methods:
            if method.name in sig_parent:
                parent_signature = sig_parent[method.name]
                child_signature = sig_child[method.name]

                if parent_signature != child_signature:
                    raise RedefinedMethodError(method.name)

    def __add_method_from_parent_to_child(self, _cl, p_methods, c_methods):
        for method in p_methods:
            if method.name not in c_methods:
                copied_method = deepcopy(method)
                # Add at the beginning
                _cl.feature_list.insert(0, copied_method)

    def __add_attr_from_parent_to_child(self, _cl, attrs_of_parent):
        for attr in attrs_of_parent:
            # Add at the beginning
            _cl.feature_list.insert(0, deepcopy(attr))

    def __check_scope_and_type(self, _class):
        """
            Check scope and type for each class.

            If it's a method, goes recursively inside the body.

            When a scope is created?
            With a new block, let, case,

            OBS: Every attribute is private and every method is public.
        """

        for feature in _class.feature_list:
            _type = returned_type(feature, _class)

            if isAttribute(feature):
                value_type = get_expression_type(feature.body, _class,
                                                 self.scope)
                # Test if the attribute value type is the same as declared.
                if feature.type != value_type:
                    raise AttributeTypeError(feature, value_type)

                self.scope.add(feature.name, _type)

            elif isMethod(feature):
                self.scope.add(feature.name, _type)

                # Add arguments to scope. name:type
                for formal in feature.formal_list:
                    self.scope.add(formal[0], formal[1])

                self.__check_children(feature.body, _class)

    def __check_children(self, expression, _class):
        if isinstance(expression, Block):
            self.scope.new()

            for expr in expression.body:
                self.__check_children(expr, _class)

            self.scope.destroy()

        elif isinstance(expression, Dispatch):
            self.__check_children(expression.body, _class)

            # Get return type
            if expression.body == 'self':
                _class_name = _class.name
            else:
                _class_name = expression.body.return_type

            # Get the whole class' structure
            _class_content = self.classes[_class_name]

            called_method = False

            # Parse the structure untill match the method name
            for feature in _class_content.feature_list:
                if isMethod(feature) and feature.name == expression.method:
                    called_method = True

                    if len(feature.formal_list) != len(expression.expr_list):
                        raise NumberOfArgumentError(feature.name, _class_name)

                    formals = zip(
                        feature.formal_list,
                        expression.expr_list,
                    )

                    # Test if the arguments types are not equals
                    for feat, called in formals:
                        expression_type = get_expression_type(
                            called, _class, self.scope)
                        # feat[0] is the name and feat[1] the type
                        if feat[1] != expression_type:
                            raise ArgumentTypeError(feature, _class_name)

                    # Test if the returns types are not equals
                    called_method_type = _class_name
                    feature_type = returned_type(feature, _class)

                    if feature_type != called_method_type:
                        raise ReturnedTypeError(feature.name, _class_name)

            # If didn't match the method name...
            if not called_method:
                raise UndefinedMethodError(expression.method, _class_name)

        elif isinstance(expression, Let):
            self.scope.new()
            self.scope.add(expression.object, expression.type)

            # Test if the declared type is the same type as
            # the given value
            value_type = get_expression_type(expression.init, _class,
                                             self.scope)
            if expression.type != value_type:
                raise DeclaredTypeError(expression.type, value_type)

            self.__check_children(expression.body, _class)

            self.scope.destroy()

        elif isinstance(expression, While):
            self.__check_children(expression.predicate, _class)
            self.__check_children(expression.body, _class)
            # If the methods above did not raise an error, means that
            # the body type is Int or an Object.
            # If is an Object and is not a Bool, must raise an error.
            self.__raise_if_not_bool(expression, _class, 'While')

        elif isinstance(expression, Lt) or isinstance(expression, Le):
            first_type, second_type = self.__get_params_types(
                expression, _class)

            if first_type != 'Int' or second_type != 'Int':
                raise TypeCheckError(first_type, second_type, _class)

        elif isinstance(expression, Eq):
            """
                The comparison = is a special case.
                If either <expr1> or <expr2> has static type Int, Bool,
                or String, then the other must have the same static type.
            """
            first_type, second_type = self.__get_params_types(
                expression, _class)
            types = ['String', 'Int', 'Bool']
            if first_type not in types or second_type not in types:
                raise EqualTypeCheckError(first_type, second_type, _class)

            if first_type != second_type:
                raise EqualCheckError(first_type, second_type, _class)

        elif any(isinstance(expression, X) for X in [Plus, Sub, Mult, Div]):
            """
                The static types of the two sub-expressions must be Int.

                Cool has only integer division.
            """
            first_type, second_type = self.__get_params_types(
                expression, _class)

            if first_type != 'Int' or second_type != 'Int':
                raise ArithmeticError(first_type, second_type, _class)

        elif isinstance(expression, Assign):
            self.__check_children(expression.body, _class)
            # If the method above did not raise an error, means that
            # the body type is Int. Just need to test name type now.
            name_type = get_expression_type(expression.name, _class,
                                            self.scope)

            if name_type != 'Int':
                raise AssignError(name_type, 'Int', _class)

        elif isinstance(expression, If):
            self.__check_children(expression.predicate, _class)
            self.__check_children(expression.then_body, _class)
            self.__check_children(expression.else_body, _class)
            # If the methods above did not raise an error, means that
            # the body type is Int or an Object.
            # If is an Object and is not a Bool, must raise an error.
            self.__raise_if_not_bool(expression, _class, 'If')

    def __raise_if_not_bool(self, expression, _class, statement):
        if isinstance(expression.predicate, Object):
            obj_type = get_expression_type(expression.predicate, _class,
                                           self.scope)
            if obj_type != 'Bool':
                raise ConditionStatementError(statement, obj_type, _class)

    def __get_params_types(self, expression, _class):
        first_type = get_expression_type(expression.first, _class, self.scope)
        second_type = get_expression_type(expression.second, _class,
                                          self.scope)
        return first_type, second_type