def visit(self, node: ast.MethodCallNode, scope: Scope): obj_instance = self.visit(node.obj, scope) if isinstance(obj_instance, VoidInstance): raise ExecutionError(err.VOID_EXPRESSION) if obj_instance.type.conforms_to(self.context.get_type('Object')) and ('Object', node.id) in defaults: args = (obj_instance,) + tuple(self.visit(arg, scope) for arg in node.args) + (self.context,) return defaults['Object', node.id](*args) if obj_instance.type.conforms_to(self.context.get_type('IO')) and ('IO', node.id) in defaults: args = (obj_instance,) + tuple(self.visit(arg, scope) for arg in node.args) + (self.context,) return defaults['IO', node.id](*args) if obj_instance.type.conforms_to(self.context.get_type('String')) and ('String', node.id) in defaults: args = (obj_instance,) + tuple(self.visit(arg, scope) for arg in node.args) + (self.context,) return defaults['String', node.id](*args) new_scope = Scope() method = obj_instance.get_method(node.id) new_scope.define_variable('self', obj_instance.type).instance = obj_instance for name, typex, arg in zip(method.param_names, method.param_types, node.args): new_scope.define_variable(name, typex).instance = self.visit(arg, scope) self.call_stack.append(self.current_instance) self.current_instance = obj_instance output = self.visit(method.expr, new_scope) self.current_instance = self.call_stack.pop() return output
def visit(self, node: ast.LetNode, scope: Scope): for i, (_id, _type, _expr) in enumerate(node.declarations): if _id == "self": line, column = node.declaration_names_positions[i] self.errors.append(err.SELF_USED_IN_LET % (line, column)) continue try: var_static_type = (self.context.get_type(_type) if _type != "SELF_TYPE" else self.current_type) except SemanticError: line, column = node.declaration_types_positions[i] self.errors.append(err.UNDEFINED_TYPE % (line, column, _type)) var_static_type = ErrorType() # if scope.is_local(_id): # feature = self.current_method or self.current_attribute # self.errors.append( # err.LOCAL_ALREADY_DEFINED # % (node.line, node.column, _id, feature.name) # ) # else: scope.define_variable(_id, var_static_type) expr_type = (self.visit(_expr, scope.create_child()) if _expr is not None else None) if expr_type is not None and not expr_type.conforms_to( var_static_type): self.errors.append(err.INCOMPATIBLE_TYPES % (node.line, node.column, expr_type.name, var_static_type.name)) return self.visit(node.expr, scope.create_child())
def visit(self, node: ast.AttrDeclarationNode, scope: Scope): # Solve the expression of the attribute expr_node = (self.visit(node.expr, scope.create_child()) if node.expr is not None else None) # Define attribute in the scope var_info = scope.define_variable(node.id, self.context.get_type(node.type)) # Set and get the reference to the variable info node var_info_node = self.variables[var_info] = VariableInfoNode( self.context.get_type(node.type), var_info) if node.type == "AUTO_TYPE": # Get the reference to the attribute node attr_node = self.attributes[self.current_type.name, node.id] # If the expression node is not None then two edges are creates in the graph if expr_node is not None: self.graph.add_edge(expr_node, var_info_node) self.graph.add_edge(expr_node, attr_node) # Finally a cycle of two nodes is created between var_info_node and attr_node self.graph.add_edge(var_info_node, attr_node) self.graph.add_edge(attr_node, var_info_node)
def visit(self, node: ast.LetNode, scope: Scope): for _id, _type, _expr in node.declarations: try: # Define and get the var_info var_info = scope.define_variable(_id, self.context.get_type(_type)) except SemanticError: var_info = scope.define_variable(_id, ErrorType()) var_info_node = self.variables[var_info] = VariableInfoNode( var_info.type, var_info) expr_node = (self.visit(_expr, scope.create_child()) if _expr is not None else None) if var_info.type.name == "AUTO_TYPE": # Create an edge or add an new node only if it is AutoType if expr_node is not None: self.graph.add_edge(expr_node, var_info_node) if expr_node.type.name == "AUTO_TYPE": self.graph.add_edge(var_info_node, expr_node) else: self.graph.add_node(var_info_node) elif expr_node is not None and expr_node.type.name == "AUTO_TYPE": self.graph.add_edge(var_info_node, expr_node) return self.visit(node.expr, scope.create_child())
def visit(self, node: ast.AttrDeclarationNode, scope: Scope): if node.id == "self": self.errors.append(err.SELF_INVALID_ATTRIBUTE_ID % (node.line, node.column)) try: attr_type = (self.context.get_type(node.type) if node.type != "SELF_TYPE" else self.current_type) except SemanticError: attr_type = ErrorType() scope.define_variable("self", self.current_type) # set the current attribute for analyze the body # and set the self.current_method variable to None self.current_attribute = self.current_type.get_attribute(node.id) self.current_method = None if node.expr is not None: expr_type = self.visit(node.expr, scope.create_child()) if not expr_type.conforms_to(attr_type): line, column = node.expr_position self.errors.append( err.INCOMPATIBLE_TYPES % (line, column, expr_type.name, attr_type.name)) scope.define_variable(node.id, attr_type)
def visit(self, node: ast.MethodDeclarationNode, scope: Scope): self.current_method = self.current_type.get_method(node.id) # Define 'self' as a variable in the scope self_var = scope.define_variable('self', self.current_type) # Set the reference of 'self' variable info node self.variables[self_var] = VariableInfoNode(self.current_type, self_var) param_names = self.current_method.param_names param_types = self.current_method.param_types for i, (param_name, param_type) in enumerate(zip(param_names, param_types)): # Define parameter as local variable in current scope param_var_info = scope.define_variable(param_name, param_type) # Set the reference to the variable info node param_var_info_node = self.variables[param_var_info] = VariableInfoNode(param_type, param_var_info) if param_type.name == 'AUTO_TYPE': # Get the parameter node parameter_node = self.methods[self.current_type.name, self.current_method.name][0][i] # Create the cycle of two nodes between param_var_info_node and parameter_node self.graph.add_edge(param_var_info_node, parameter_node) self.graph.add_edge(parameter_node, param_var_info_node) # Solve the body of the method body_node = self.visit(node.body, scope) if self.current_method.return_type.name == 'AUTO_TYPE': # Get the return type node and add an edge body_node -> return_type_node return_type_node = self.methods[self.current_type.name, self.current_method.name][1] self.graph.add_edge(body_node, return_type_node)
def visit(self, node: ast.ProgramNode, scope: Scope = None): if scope is None: scope = Scope() for elem in node.declarations: self.visit(elem, scope.create_child()) return scope
def visit(self, node: ast.ProgramNode, scope: Scope = None): if scope is None: scope = Scope() for item in node.declarations: self.visit(item, scope.create_child()) # print(self.graph, '\n') self.graph.update_dependencies(default_type=self.context.get_type('Object')) # print(self.graph, '\n') InferenceTypeSubstitute(self.context, self.errors).visit(node, scope)
def visit(self, node: ast.LetNode, scope: Scope): default = {'String': '', 'Int': 0, 'Bool': False} for _id, _type, _expr in node.declarations: instance = self.visit(_expr, scope.create_child()) if _expr is not None else VoidInstance() if _expr is None and _type in default: instance = Instance(self.context.get_type(_type), default[_type]) scope.define_variable(_id, instance.type).instance = instance return self.visit(node.expr, scope.create_child())
def visit(self, node: ast.AssignNode, scope: Scope): var_info = scope.find_variable(node.id) expr_type = self.visit(node.expr, scope.create_child()) if var_info is None: self.errors.append(err.VARIABLE_NOT_DEFINED % (node.id, self.current_method.name)) else: if not expr_type.conforms_to(var_info.type): self.errors.append(err.INCOMPATIBLE_TYPES % (expr_type.name, var_info.type.name)) return expr_type
def visit(self, node: ast.AttrDeclarationNode, scope: Scope): if node.id == 'self': self.errors.append(err.SELF_INVALID_ATTRIBUTE_ID) attr_type = self.context.get_type( node.type) if node.type != 'SELF_TYPE' else self.current_type if node.expr is not None: expr_type = self.visit(node.expr, scope.create_child()) if not expr_type.conforms_to(attr_type): self.errors.append(err.INCOMPATIBLE_TYPES % (expr_type.name, attr_type.name)) scope.define_variable(node.id, attr_type)
def run(file: str, verbose: bool = False): ast, parser = parse(file, verbose) if ast is not None: ast, _, context, errors = check_semantics(ast, Scope(), Context(), []) if not errors and not parser.contains_errors: try: Executor(context).visit(ast, Scope()) typer.echo('Program finished...') except ExecutionError as e: typer.echo(e.text, err=True) for error in errors: typer.echo(error, err=True)
def compile(input_file: typer.FileText = typer.Argument(..., help='Cool file'), output_file: typer.FileTextWrite = typer.Argument( 'a.mips', help='Mips file'), verbose: bool = typer.Option(False, help='Run in verbose mode.')): # In case of encoding conflict if input_file.encoding.lower != 'utf-8': input_file = open(input_file.name, encoding='utf-8') program = input_file.read() tokens, lexer = tokenize(program, verbose) if lexer is None or lexer.contain_errors: exit(1) if not tokens[:-1]: # there is always at least the EOF token log_error('(0, 0) - SyntacticError: ERROR at or near EOF') exit(1) ast, parser = parse(tokens, verbose) # parsing process failed if ast is None: exit(1) PositionAssigner(tokens).visit(ast) ast, _, _, errors = check_semantics(ast, Scope(), Context(), []) if errors or parser.contains_errors: for e in errors: log_error(e) exit(1) exit(0)
def visit(self, node: ast.SwitchCaseNode, scope: Scope): self.visit(node.expr, scope) types = [] visited = set() for i, (identifier, type_name, expr) in enumerate(node.cases): new_scope = scope.create_child() try: if type_name != "SELF_TYPE": new_scope.define_variable(identifier, self.context.get_type(type_name)) else: self.errors.append(err.INVALID_CASE_TYPE % type_name) except SemanticError: new_scope.define_variable(identifier, ErrorType()) line, column = node.cases_positions[i] self.errors.append(err.UNDEFINED_TYPE_IN_BRANCH % (line, column, type_name)) # Cannot be dublicate Branches types if type_name in visited: line, column = node.cases_positions[i] self.errors.append(err.DUPLICATE_BARNCH_IN_CASE % (line, column, type_name)) visited.add(type_name) types.append(self.visit(expr, new_scope)) return Type.multi_join(types)
def visit(self, node: ast.ConditionalNode, scope: Scope): if_node = self.visit(node.if_expr, scope) if not isinstance(if_node, AtomNode): self.graph.add_edge(AtomNode(self.context.get_type("Bool")), if_node) then_node = self.visit(node.then_expr, scope.create_child()) else_node = self.visit(node.else_expr, scope.create_child()) if isinstance(then_node, AtomNode) and isinstance(else_node, AtomNode): return AtomNode(then_node.type.join(else_node.type)) conditional_node = ConditionalNode(self.context.get_type("AUTO_TYPE"), then_node, else_node) if isinstance(then_node, AtomNode) and not isinstance(else_node, AtomNode): self.graph.add_edge(then_node, else_node) elif not isinstance(then_node, AtomNode) and isinstance( else_node, AtomNode): self.graph.add_edge(else_node, then_node) else: self.graph.add_edge(then_node, else_node) self.graph.add_edge(else_node, then_node) self.graph.add_edge(conditional_node, then_node) self.graph.add_edge(conditional_node, else_node) return conditional_node
def visit(self, node: ast.SwitchCaseNode, scope: Scope): self.visit(node.expr, scope) defined_nodes = [] not_defined_nodes = [] case_nodes = [] for _id, _type, _expr in node.cases: new_scope = scope.create_child() var_info = new_scope.define_variable(_id, self.context.get_type(_type)) self.variables[var_info] = VariableInfoNode( var_info.type, var_info) case_node = self.visit(_expr, new_scope) if isinstance(case_node, AtomNode): defined_nodes.append(case_node) else: not_defined_nodes.append(case_node) case_nodes.append(case_node) if any(e.type.name == "AUTO_TYPE" for e in case_nodes): if defined_nodes: t = Type.multi_join([x.type for x in defined_nodes]) for x in not_defined_nodes: self.graph.add_edge(AtomNode(t), x) case_of_node = CaseOfNode(self.context.get_type("AUTO_TYPE"), case_nodes) self.graph.add_node(case_of_node) return case_of_node return AtomNode(Type.multi_join([e.type for e in case_nodes]))
def visit(self, node: ast.VariableNode, scope: Scope): variable = scope.find_variable(node.lex) if variable is None: self.errors.append(err.VARIABLE_NOT_DEFINED % (node.lex, self.current_method.name)) return ErrorType() return variable.type
def visit(self, node: ast.WhileNode, scope: Scope): condition = self.visit(node.condition, scope) if condition != self.context.get_type('Bool'): self.errors.append(err.INCOMPATIBLE_TYPES % (condition.name, 'Bool')) self.visit(node.body, scope.create_child()) return self.context.get_type('Object')
def test_inference(): programs, results = get_programs('inference') for program, result in zip(programs, results): tokens, _ = tokenize(program) ast, _ = parse(tokens) ast, _, _, errors = check_semantics(ast, Scope(), Context(), []) assert not errors and CodeBuilder().visit(ast, 0) == result
def visit(self, node: ast.MethodDeclarationNode, scope: Scope): self.current_method = self.current_type.get_method(node.id) # Parameters can hide the attribute declaration, that's why we are not checking if there is defined, # instead we are checking for local declaration. Also it is checked that the static type of a parameter is # different of SELF_TYPE. scope.define_variable('self', self.current_type) for param_name, param_type in zip(self.current_method.param_names, self.current_method.param_types): if not scope.is_local(param_name): if param_type.name == 'SELF_TYPE': self.errors.append(err.INVALID_PARAM_TYPE % 'SELF_TYPE') scope.define_variable(param_name, ErrorType()) else: scope.define_variable( param_name, self.context.get_type(param_type.name)) else: self.errors.append(err.LOCAL_ALREADY_DEFINED % (param_name, self.current_method.name)) return_type = self.context.get_type( node.return_type ) if node.return_type != 'SELF_TYPE' else self.current_type expr_type = self.visit(node.body, scope) if not expr_type.conforms_to(return_type): self.errors.append(err.INCOMPATIBLE_TYPES % (expr_type.name, return_type.name))
def visit(self, node: ast.WhileNode, scope: Scope): condition = self.visit(node.condition, scope) if condition != self.context.get_type("Bool"): self.errors.append( err.INCOMPATIBLE_TYPES % (node.line, node.column, condition.name, "Bool")) self.visit(node.body, scope.create_child()) return self.context.get_type("Object")
def visit(self, node: ast.AssignNode, scope: Scope): var_info = scope.find_variable(node.id) expr_node = self.visit(node.expr, scope.create_child()) if var_info is not None: if expr_node.type.name != 'AUTO_TYPE' and var_info.type.name == 'AUTO_TYPE': self.graph.add_edge(expr_node, self.variables[var_info]) elif var_info.type.name != 'AUTO_TYPE' and expr_node.type.name == 'AUTO_TYPE': self.graph.add_edge(AtomNode(self.context.get_type(var_info.type.name)), expr_node) elif var_info.type.name == 'AUTO_TYPE' and expr_node.type.name == 'AUTO_TYPE': # Create a cycle self.graph.add_edge(expr_node, self.variables[var_info]) self.graph.add_edge(self.variables[var_info], expr_node) else: pass return expr_node
def infer(file: str, verbose: bool = False): ast, _ = parse(file, verbose) if ast is not None: ast, _, _, errors = check_semantics(ast, Scope(), Context(), []) if errors: for e in errors: typer.echo(e, err=True) typer.echo(CodeBuilder().visit(ast, 0))
def visit(self, node: ast.AssignNode, scope: Scope): variable_info = scope.find_variable(node.id) if variable_info is None: self.current_instance.set_attribute_instance(node.id, self.visit(node.expr, scope)) return self.current_instance.get_attribute_instance(node.id) variable_info.instance = self.visit(node.expr, scope) return variable_info.instance
def test_semantic(): programs, results = get_programs('semantic') for code, result in zip(programs, results): tokens, _ = tokenize(code) ast, parser = parse(tokens) ast, _, _, errors = check_semantics(ast, Scope(), Context(), []) assert (parser.contains_errors or errors) and '\n'.join(parser.errors + errors) == result
def visit(self, node: ast.VariableNode, scope: Scope): var_info = scope.find_variable(node.lex) if var_info is not None: if var_info.type.name == "AUTO_TYPE": return self.variables[var_info] else: return AtomNode(var_info.type) else: return None
def visit(self, node: ast.AssignNode, scope: Scope): var_info = scope.find_variable(node.id) if var_info.name == "self": self.errors.append(err.SELF_IS_READONLY % (node.line, node.column)) expr_type = self.visit(node.expr, scope.create_child()) if var_info is None: self.errors.append( err.UNDEFINED_VARIABLE % (node.line, node.column, node.id, self.current_method.name)) else: if not expr_type.conforms_to(var_info.type): self.errors.append(err.INCOMPATIBLE_TYPES % (node.line, node.column, expr_type.name, var_info.type.name)) return expr_type
def visit(self, node: ast.InstantiateNode, scope: Scope): default = None if node.lex == 'String': default = '' elif node.lex == 'Int': default = 0 elif node.lex == 'Bool': default = False instance = Instance(self.context.get_type(node.lex), default) self.call_stack.append(self.current_instance) self.current_instance = instance fake_scope = Scope() for attr, _ in instance.type.all_attributes(): attr_instance = self.visit(attr.expr, fake_scope) if attr.expr is not None else VoidInstance() fake_scope.define_variable(attr.name, attr.type).instance = attr_instance self.current_instance.set_attribute_instance(attr.name, attr_instance) self.current_instance = self.call_stack.pop() return instance
def visit(self, node: ast.AttrDeclarationNode, scope: Scope): attr_type = self.context.get_type(node.type) var_info = scope.find_variable(node.id) if node.expr is not None: self.visit(node.expr, scope.children[node.index]) if attr_type == self.context.get_type("AUTO_TYPE"): if var_info.type == self.context.get_type("AUTO_TYPE"): self.errors.append(err.INFERENCE_ERROR_ATTRIBUTE % node.id) node.type = var_info.type.name
def visit(self, node: ast.ClassDeclarationNode, scope: Scope): self.current_type = self.context.get_type(node.id) attrs = [feature for feature in node.features if isinstance(feature, ast.AttrDeclarationNode)] methods = [feature for feature in node.features if isinstance(feature, ast.MethodDeclarationNode)] for attr in attrs: self.visit(attr, scope) for method in methods: self.visit(method, scope.create_child())