From bb0238a28c984d533bc04a69b18a9e2864e36633 Mon Sep 17 00:00:00 2001 From: RodroVMS Date: Thu, 25 Feb 2021 19:18:53 -0500 Subject: [PATCH] Added Autotype Collector. --- src/devdeb.py | 2 + src/parsing/ast.py | 2 +- src/semantics/autotype_collector.py | 269 +++++++++++++++++++++++++++- src/semantics/tools.py | 179 +++++++++++++++++- src/semantics/type_builder.py | 6 +- src/semantics/utils.py | 139 ++++++++++---- 6 files changed, 542 insertions(+), 55 deletions(-) create mode 100644 src/devdeb.py diff --git a/src/devdeb.py b/src/devdeb.py new file mode 100644 index 000000000..ef174c7e1 --- /dev/null +++ b/src/devdeb.py @@ -0,0 +1,2 @@ +def run_pipeline(): + pass \ No newline at end of file diff --git a/src/parsing/ast.py b/src/parsing/ast.py index 5d0803779..cd8f0f8c9 100644 --- a/src/parsing/ast.py +++ b/src/parsing/ast.py @@ -52,7 +52,7 @@ class ExpressionNode(Node): class VarDeclarationNode(ExpressionNode): def __init__(self, idx, typex, expression=None): self.id = idx - self.typex = typex + self.type = typex self.expr = expression diff --git a/src/semantics/autotype_collector.py b/src/semantics/autotype_collector.py index 2a74ee36c..da2dcc28a 100644 --- a/src/semantics/autotype_collector.py +++ b/src/semantics/autotype_collector.py @@ -1,6 +1,7 @@ +from semantics.utils import conforms, join, join_list, smart_add import semantics.visitor as visitor -from semantics.tools import Context, Scope -from parsing.ast import AttrDeclarationNode, ClassDeclarationNode, ProgramNode +from semantics.tools import Context, ErrorType, Scope, SelfType, SemanticError, TypeBag +from parsing.ast import ArithmeticNode, AssignNode, AttrDeclarationNode, BlocksNode, BooleanNode, CaseNode, CaseOptionNode, ClassDeclarationNode, ComparerNode, ComplementNode, ConditionalNode, InstantiateNode, IntNode, IsVoidNode, LetNode, LoopNode, MethodCallNode, MethodDeclarationNode, NotNode, ProgramNode, StringNode, VarDeclarationNode, VariableNode class AutotypeCollector: def __init__(self, context:Context): @@ -35,14 +36,268 @@ def visit(self, node, scope): @visitor.when(AttrDeclarationNode) def visit(self, node, scope): - self.current_attrb = self.current_type.get_attribute(node.id) - node_type = self.update_type(self.current_attrb.type) - + node_type = self.current_type.get_attribute(node.id).swap_self_type(self.current_type) if not node.expr: - self.current_attrb = None node.inferenced_type = node_type return + + self.visit(node.expr, scope) + node_expr = node.expr.inferenced_type + node_expr = conforms(node_expr, node_type) + + var = scope.find_variable(node.id) + var.type = node_type + + node.inferenced_type = node_type + + @visitor.when(MethodDeclarationNode) + def visit(self, node, scopex): + scope = scopex.create_child() + current_method = self.current_type.get_method(node.id) + for idx, typex in zip(current_method.param_names, current_method.param_types): + scope.define_variable(idx, typex) + + self.visit(node.body, scope) + ret_type_decl = self.current_method.return_type.swap_self_type(self.current_type) + ret_type_expr = node.body.inferenced_type + ret_type_expr = conforms(ret_type_expr, ret_type_decl) + node.body.inferenced_type = ret_type_expr + + node.inferenced_type = ret_type_decl.clone() + ret_type_decl.swap_types(SelfType(), self.current_type) + + @visitor.when(BlocksNode) + def visit(self, node, scope): + for expr in node.body: + self.visit(expr, scope) + node.inferenced_type = node.body[-1].inferenced_type + + @visitor.when(ConditionalNode) + def visit(self, node, scope): + self.visit(node.condition) + condition_type = node.condition.inferenced_type + bool_type = self.context.get_type("Bool") + conforms(condition_type, bool_type) + + self.visit(node.then_body) + then_type = node.then_body.inferenced_type + self.visit(node.else_body) + else_type = node.else_body.inferenced_type + + joined_type = join(then_type, else_type) + node.inferenced_type = joined_type + + @visitor.when(CaseNode) + def visit(self, node, scope:Scope): + self.visit(node.expr, scope) + + type_list = [] + for var in node.casevars: + child = scope.create_child() + self.visit(var, child) + type_list.append(var.inferenced_type) + + joined_type = join_list(type_list) + node.inferenced_type = joined_type + + @visitor.when(CaseOptionNode) + def visit(self, node, scope): + try: + node_type = self.context.get_type(node.type, selftype=False, autotype=False) + except SemanticError as err: + node_type = ErrorType() + + scope.define_variable(node.id, node_type) + self.visit(node.expr, scope) + node.inferenced_type = node.expr.inferenced_type + + @visitor.when(LoopNode) + def visit(self, node, scope): + self.visit(node.condition, scope) + condition_type = node.condition.inferenced_type + bool_type = self.context.get_type("Bool") + conforms(condition_type, bool_type) + + self.visit(node.bodyexpr, scope) + node.inferenced_type = self.context.get_type("Object") + + @visitor.when(LetNode) + def visit(self, node, scope): + child = scope.create_child() + for var in node.var_decl: + self.visit(var, child) + self.visit(node.in_expr, scope) + node.inferenced_type = node.in_expr.inferenced_type + + @visitor.when(VarDeclarationNode) + def visit(self, node, scope): + try: + node_type = self.context.get_type(node.type).swap_self_type(self.current_type) + except SemanticError as err: + node_type = ErrorType() + + if not scope.is_local(node.id): + scope.define_variable(node.id, node_type) + node.defined = True + else: + #add error + node.defined = False + + if node.expr: + self.visit(node.expr, scope) + expr_type = node.expr.inferenced_type + conforms(expr_type, node_type) + node.expr.inferenced_type = expr_type + + node.inferenced_type = node_type + + @visitor.when(AssignNode) + def visit(self, node, scope:Scope): + var = scope.find_variable(node.id) + if not var: + var_type = ErrorType() + node.defined = False + else: + var_type = var.type.swap_self_type(self.current_type) + node.defined = True + + self.visit(node.expr, scope) + node_expr = node.expr.inferenced_type + + if var and var.name != 'self': + conforms(node_expr, var_type) + var.type = var_type + node.inferenced_type = var_type + + @visitor.when(MethodCallNode) + def visit(self, node, scope): + if node.expr == None: + caller = self.current_type + elif node.type == None: + self.visit(node.expr) + caller = node.expr.inferenced_type + else: + self.visit(node.expr) + bridge = node.expr.inferenced_type + caller = self.context.get_type(node.type, selftype=False, autotype=False) + conforms(bridge, caller) + + methods = None + if len(caller.type_set) > 1: + methods_by_name = self.context.get_method_by_name(node.id, len(node.args)) + types = [typex for _, typex in methods_by_name] + conforms(caller, TypeBag(set(types))) + if len(caller.type_set): + methods = [(t, t.get_method) for t in caller.heads] + else: + pass #Add Error + elif len(caller.type_set) == 1: + caller_type = caller.heads[0] + try: + methods = [caller_type, caller_type.get_method(node.id)] + except SemanticError: + pass #Add Error + + if methods: + type_set = set() + heads = [] + for typex, method in methods: + ret_type = method.return_type.clone() + ret_type.swap_self_type(typex) + smart_add(type_set, heads, ret_type) + for i in range(len(node.args)): + arg, param_type = node.args[i], method.param_types[i] + self.visit(arg, scope) + arg_type = arg.inferenced_type + conforms(arg_type, param_type) + node.inferenced_type = TypeBag(type_set, heads) + else: + node.inferenced_type = ErrorType() + + @visitor.when(ArithmeticNode) + def visit(self, node, scope): + self.visit(node.left, scope) + left_type = node.left.inferenced_type + + self.visit(node.right, scope) + right_type = node.right.inferenced_type + + int_type = self.context.get_type("Int") + conforms(left_type, int_type) + conforms(right_type, int_type) + node.inferenced_type = int_type + + @visitor.when(ComparerNode) + def visit(self, node, scope): + self.visit(node.left, scope) + left_type = node.left.inferenced_type + + self.visit(node.right, scope) + right_type = node.right.inferenced_type + + conforms(left_type, right_type) + conforms(right_type, left_type) + node.inferenced_type = self.context.get_type("Bool") + + @visitor.when(VariableNode) + def visit(self, node, scope): + var = scope.find_variable(node.expr) + if var: + node.defined = True + var_type = var.type + else: + node.defined = False + var_type = ErrorType() + node.inferenced_type = var_type + + @visitor.when(NotNode) + def visit(self, node, scope): + self.visit(node.expr, scope) + expr_type = node.expr.inferenced_type + bool_type = self.context.get_type("Bool") + conforms(expr_type, bool_type) + + node.inferenced_type = bool_type + + @visitor.when(ComplementNode) + def visit(self, node, scope): + self.visit(node.expr, scope) + expr_type = node.expr.inferenced_type + int_type = self.context.get_type("int") + conforms(expr_type, int_type) + + node.inferenced_type = int_type + + @visitor.when(IsVoidNode) + def visit(self, node, scope): + self.visit(node.expr, scope) + node.inferenced_type = self.context.get_type("Bool") + + @visitor.when(InstantiateNode) + def visit(self, node, scope): + try: + node_type = self.context.get_type(node.expr, selftype=False, autotype=False) + except SemanticError as err: + node_type = ErrorType() + node.inferenced_type = node_type + + @visitor.when(IntNode) + def visit(self, node, scope): + node.inferenced_type = self.context.get_type("Int") + + @visitor.when(StringNode) + def visit(self, node, scope): + node.inferenced_type = self.context.get_type("String") + + @visitor.when(BooleanNode) + def visit(self, node, scope): + node.inferenced_type = self.context.get_type("Bool") + # todo: Revisar los auto types que me hace falta y que no -# todo: completar de manera acorde el autotype collector \ No newline at end of file +# todo: completar de manera acorde el autotype collector +# todo: Annadir error en VarDeclarationNode +# todo: Annadir error en MethodCallNode (2) +# todo: annadir error en INsyantiate Node +# todo: Cambiar self.error a que cada error tengo la tupla de localizacion, asi permite organizar los errores \ No newline at end of file diff --git a/src/semantics/tools.py b/src/semantics/tools.py index 4b32c2cc2..f0dd3b0af 100644 --- a/src/semantics/tools.py +++ b/src/semantics/tools.py @@ -1,5 +1,7 @@ import itertools as itt from collections import OrderedDict +from typing import FrozenSet +from semantics.utils import from_dict_to_set class InternalError(Exception): @property @@ -73,6 +75,151 @@ def get_attribute(self, name:str): return self.parent.get_attribute(name) except SemanticError: raise AttributeError(f'Attribute "{name}" is not defined in {self.name}.') + + def get_method(self, name:str, local:bool = False): + try: + return next(method for method in self.methods if method.name == name) + except StopIteration: + if self.parent is None: + raise SemanticError(f'Method "{name}" is not defined in class {self.name}.') + try: + return self.parent.get_method(name) + except SemanticError: + raise SemanticError(f'Method "{name}" is not defined in class {self.name}.') + + def define_method(self, name:str, param_names:list, param_types:list, return_type): + if name in (method.name for method in self.methods): + raise SemanticError(f'Method \'{name}\' already defined in \'{self.name}\'') + + try: + parent_method = self.get_method(name) + except SemanticError: + parent_method = None + if parent_method: + error_list = [] + if not return_type.conforms_to(parent_method.return_type): + error_list.append(f" -> Same return type: Redefined method has \'{return_type.name}\' as return type instead of \'{parent_method.return_type.name}\'.") + if len(param_types) != len(parent_method.param_types): + error_list.append(f" -> Same amount of params: Redefined method has {len(param_types)} params instead of {len(parent_method.param_types)}.") + else: + count = 0 + err = [] + for param_type, parent_param_type in zip(param_types, parent_method.param_types): + if param_type != parent_param_type: + err.append(f" -Param number {count} has {param_type.name} as type instead of {parent_param_type.name}") + count += 1 + if err: + s = f" -> Same param types:\n" + "\n".join(child for child in err) + error_list.append(s) + if error_list: + err = f"Redifined method \"{name}\" in class {self.name} does not have:\n" + "\n".join(child for child in error_list) + raise SemanticError(err) + + method = Method(name, param_names, param_types, return_type) + self.methods.append(method) + return method + + def all_attributes(self, clean=True): + plain = OrderedDict() if self.parent is None else self.parent.all_attributes(False) + for attr in self.attributes: + plain[attr.name] = (attr, self) + return plain.values() if clean else plain + + def all_methods(self, clean=True): + plain = OrderedDict() if self.parent is None else self.parent.all_methods(False) + for method in self.methods: + plain[method.name] = (method, self) + return plain.values() if clean else plain + + def conforms_to(self, other): + return other.bypass() or self == other or self.parent is not None and self.parent.conforms_to(other) + + def bypass(self): + return False + + def least_common_ancestor(self, other): + this = self + if isinstance(this, ErrorType) or isinstance(other, ErrorType): + return ErrorType() + #raise SemanticError("Error Type detected while perfoming Join. Aborting.") + + while this.index < other.index: + other = other.parent + while other.index < this.index: + this = this.parent + if not (this and other): + return None + while this.name != other.name: + this = this.parent + other = other.parent + if this == None: + return None + return this + +class TypeBag: + def __init__(self, type_set, heads = []) -> None: + self.type_set:set = type_set if isinstance(type_set, set) else from_dict_to_set(type_set) + self.heads:list = heads + if len(type_set) == 1: + self.heads = list(self.type_set) + self.condition_list = [] + self.conform_list = [] + + def set_conditions(self, condition_list, conform_list): + self.condition_list = condition_list + self.conform_list = conform_list + self.update_type_set_from_conforms() + + def update_type_set_from_conforms(self): + intersect_set = set() + for conform_set in self.conforms_list: + intersect_set = intersect_set.union(conform_set) + self.type_set = self.type_set.intersection(intersect_set) + self.update_heads() + + def update_heads(self): + new_heads = [] + visited = set() + for head in self.upper_limmit: + if head in self.type_set: + new_heads.append(head) + continue + new_heads = [] + lower_index = 2**32 + for typex in self.type_set: + if typex in visited: + continue + if typex.conforms_to(head): + visited.add(typex) + if typex.index < lower_index: + new_heads = [typex] + lower_index = typex.index + elif typex.index == lower_index: + new_heads.append(typex) + new_heads += new_heads + self.upper_limmit = new_heads + + def swap_self_type(self, update_type): + try: + self.type_set.remove(SelfType()) + self.type_set.add(update_type) + except KeyError: + pass + return self + + def swap_types(self, update_type, remove_type): + try: + self.type_set.remove(remove_type) + self.type_set.add(update_type) + except KeyError: + pass + return self + + def clone(self): + clone = TypeBag(self.type_set, self.heads) + clone.condition_list = self.condition_list + clone.conform_list = self.conform_list + return clone class SelfType(Type): def __init__(self): @@ -80,15 +227,17 @@ def __init__(self): def conforms_to(self, other): #if isinstance(other, SelfType): # return True - raise InternalError("SELF_TYPE yet to be assigned, cannot conform.") + raise InternalError("SELF_TYPE is yet to be assigned, cannot conform.") def bypass(self): - raise InternalError("SELF_TYPE yet to be assigned, cannot bypass.") + raise InternalError("SELF_TYPE is yet to be assigned, cannot bypass.") class AutoType(Type): pass class ErrorType(Type): - pass + def __init__(self): + self.name = "" + self.type_set = FrozenSet() class Context: def __init__(self) -> None: @@ -106,15 +255,31 @@ def create_type(self, name:str) -> Type: def get_type(self, name:str, selftype=True, autotype=True) -> Type: if selftype and name == "SELF_TYPE": - return SelfType() + return TypeBag({SelfType()}) #SelfType() if autotype and name == "AUTO_TYPE": self.num_autotypes += 1 - return AutoType(f"T{self.num_autotypes}", [self.types["Object"]], self.types) + return TypeBag(self.types, [self.types['Object']]) #AutoType(f"T{self.num_autotypes}", [self.types["Object"]], self.types) try: - return self.types[name] + return TypeBag({self.types[name]}) except KeyError: raise TypeError(f'Type "{name}" is not defined.') - + + def get_method_by_name(self, name:str, args:int) -> list: + def dfs(root:str, results:list): + try: + for typex in self.type_tree[root]: + for method in self.types[typex].methods: + if name == method.name and args == len(method.param_names): + results.append((method, self.types[typex])) + break + else: + dfs(typex, results) + except KeyError: + pass + results = [] + dfs("Object", results) + return results + def __str__(self): return '{\n\t' + '\n\t'.join(y for x in self.types.values() for y in str(x).split('\n')) + '\n}' diff --git a/src/semantics/type_builder.py b/src/semantics/type_builder.py index 9e4b70b3c..98f53b865 100644 --- a/src/semantics/type_builder.py +++ b/src/semantics/type_builder.py @@ -47,7 +47,7 @@ def visit(self, node): def get_type_hierarchy(self): visited = set(["Object"]) new_order = [] - self.get_type_hierarchy("Object", self.type_graph, visited, new_order, 1) + self.dfs_type_graph("Object", self.type_graph, visited, new_order, 1) circular_heritage_errors = [] for node in self.type_graph: @@ -64,7 +64,7 @@ def get_type_hierarchy(self): return new_order - def get_type_hierarchy(self, root, graph, visited:set, new_order, index): + def dfs_type_graph(self, root, graph, visited:set, new_order, index): if not root in graph: return @@ -75,7 +75,7 @@ def get_type_hierarchy(self, root, graph, visited:set, new_order, index): if node not in {"Int", "String", "IO", "Bool", "Object"}: new_order.append(self.context.get_type(node)) self.context.get_type(node).index = index - self.get_type_hierarchy(node, graph, visited, new_order, index + 1) + self.dfs_type_graph(node, graph, visited, new_order, index + 1) def check_circular_heritage(self, root, graph, path, visited): for node in graph[root]: diff --git a/src/semantics/utils.py b/src/semantics/utils.py index 953f74326..2f9d90686 100644 --- a/src/semantics/utils.py +++ b/src/semantics/utils.py @@ -1,45 +1,110 @@ -from semantics.tools import Type, ErrorType, AutoType - -def conforms(type1:Type, type2:Type): - if isinstance(type1, ErrorType) or isinstance(type2, ErrorType): - return ErrorType() - if not isinstance(type1, AutoType) and isinstance(type2, AutoType): - type2.set_upper_limmit([type1]) - return type1 - if not isinstance(type1, AutoType): - type1 = AutoType("TEMP01", [type1], {type1}) - if not isinstance(type2, AutoType): - type2 = AutoType("TEMP02", [type2], {type2}) +from semantics.tools import Type, ErrorType, AutoType, TypeBag + +def conforms(bag1:TypeBag, bag2:TypeBag): + ordered_set = order_set_by_index(bag2.type_set) + + condition_list = [] + conform_list = [] + for condition in ordered_set: + conform = conform_to_condition(bag1.type_set, condition) + for i in range(len(condition_list)): + conform_i = conform_list[i] + if len(conform_i) == len(conform) and len(conform.intersection(conform_i)) == len(conform): + condition_list[i].add(condition) + break + else: + condition_list.append({condition}) + conform_list.append(conform) - print("type 1 set:", ", ".join(typex.name for typex in type1.type_set)) - print("type 2 set:", ", ".join(typex.name for typex in type2.type_set)) + bag1.set_conditions(condition_list, conform_list) + return bag1 + +def conform_to_condition(type_set, parent) -> set: + set_result = set() + for typex in type_set: + if typex.conforms_to(parent): + set_result.add(typex) + return set_result - condition_set_list, conform_set_list = conforming(type1, type2) - type1.set_new_conditions(condition_set_list, conform_set_list) +def join(bag1:TypeBag, bag2:TypeBag) -> TypeBag: + ancestor_set = set() + head_list = [] + ordered_set1 = order_set_by_index(bag1.type_set) + ordered_set2 = order_set_by_index(bag2.type_set) + ordered_set1, ordered_set2 = (ordered_set1, ordered_set2) if len(ordered_set1) < len(ordered_set2) else (ordered_set2, ordered_set1) + for type1 in ordered_set1: + same_branch = False + previous_ancestor = None + previous_type = None + for type2 in ordered_set2: + if same_branch and type2.conforms_to(previous_type): + previous_type = type2 + continue + common_ancestor = type1.least_common_ancestor(type2) + previous_type = type2 + if not previous_ancestor: + smart_add(ancestor_set, head_list, common_ancestor) + previous_ancestor = common_ancestor + else: + if previous_ancestor == common_ancestor: + same_branch = True + else: + same_branch = False + smart_add(ancestor_set, head_list, common_ancestor) + previous_ancestor = common_ancestor - print("Conditions obtained", [[typex.name for typex in type_set] for type_set in condition_set_list]) - print("Conforms obtained", [[typex.name for typex in type_set] for type_set in conform_set_list]) - print("Updated set:", ", ".join(typex.name for typex in type1.type_set),"\n") - return type1 - -def conforming(auto1:AutoType, auto2:AutoType): - ord_types2 = sorted(list(auto2.type_set), lambda x: x.index) - - condition_set_list = [] - conform_set_list = [] - for type2 in ord_types2: - conforms = conform_intersection(auto1.type_set, type2) - for i in range(len(condition_set_list)): - prev_conform = conform_set_list[i] - if len(prev_conform) == len(conforms) and len(conforms.intersection(prev_conform)) == len(conforms): - condition_set_list[i].add(type2) + join_result = TypeBag(ancestor_set, head_list) + return join_result + +def join_list(type_list): + typex = type_list[0] + for i in range(1, len(type_list)): + type_i = type_list[i] + typex = join(typex, type_i) + return typex + +def smart_add(type_set:set, head_list:list, typex:Type): + if isinstance(typex, TypeBag): + return auto_add(type_set, head_list, typex) + + type_set.add(typex) + there_is = False + for i in range(len(head_list)): + head = head_list[i] + ancestor = typex.least_common_ancestor(head) + if ancestor in type_set: + there_is = True + if ancestor == typex: + head_list[i] = typex break - else: - condition_set_list.append(set([type2])) - conform_set_list.append(conforms) - return condition_set_list, conform_set_list + if not there_is: + head_list.append(typex) + return head_list, type_set + +def auto_add(type_set:set, head_list:list, bag:TypeBag): + type_set = type_set.union(bag.type_set) + aux = set(bag.heads) + for i in range(len(head_list)): + head_i = head_list[i] + for head in bag.heads: + ancestor = head_i.least_common_ancestor(head) + if ancestor in type_set: + head_i[i] = ancestor + aux.pop(head) + break + head_list += [typex for typex in aux] + return head_list, type_set + +def order_set_by_index(type_set): + return sorted(list(type_set), lambda x: x.index) + +def from_dict_to_set(types:dict): + type_set = set() + for typex in types: + type_set.add(types[typex]) + return type_set -def conform_intersection(type_set, parent) -> set: +def set_intersection(parent, type_set) -> set: set_result = set() for typex in type_set: if typex.conforms_to(parent):