def _compile_while_statement(self, node: ParseTreeNode): while_start_label = Label("while_start") end_while_label = Label("while_end") self.program.append(while_start_label) condition_node = node.get_child("bool") self.oreo_to_tac(condition_node) # if the condition doesn't hold, leave the loop # IfZ a Goto L1; # > result=L1 op=IfFalseGoto, arg1=a self._add_instruction( arg1=condition_node.result, op=IF_FALSE_GOTO, result_var=end_while_label ) # the condition held, so execute the loop body self.oreo_to_tac(node.get_child("compound")) # go back to start of loop self._add_goto_instruction(while_start_label) self.program.append(end_while_label)
def _compile_if_statement(self, node: ParseTreeNode): condition_node = node.get_child("bool") self.oreo_to_tac(condition_node) condition_is_false_label = Label('if_false') # IfZ a Goto L1; # > result=L1 op=IfFalseGoto, arg1=a self._add_instruction( arg1=condition_node.result, op=IF_FALSE_GOTO, result_var=condition_is_false_label ) # the if statement was true self.oreo_to_tac(node.get_child("compound")) if node.has_child("optional_else"): end_of_else_block_label = Label('else_end') # if condition held, skip the else block self._add_goto_instruction(end_of_else_block_label) # the else block self.program.append(condition_is_false_label) self.oreo_to_tac(node.get_child("optional_else")) self.program.append(end_of_else_block_label) else: # there's no else block: if condition doesn't hold, just jump down here self.program.append(condition_is_false_label)
def assign(self, id_node: ParseTreeNode, value_node: ParseTreeNode): assert (id_node.is_terminal("ID")) identifier = id_node.get_terminal_attribute() # check that the id has been declared in scope self.use_var(id_node) self.vars[identifier].assign(id_node, value_node)
def declare(self, node: ParseTreeNode): assert (node.is_terminal("ID")) identifier = node.get_terminal_attribute() token = node.content.token if identifier in self.vars: raise ParseError(f"Redefinition of identifier {identifier}", token.line_num, token.col_num, token.context_line) else: self.vars[identifier] = ScopeEntry(token)
def _compile_print_expression(self, node: ParseTreeNode): if node.has_child("GET"): return self._add_instruction( op="GET" ) else: self._add_instruction( result_var=node.get_child("expression").result, op=node.get_a_child(["PRINT", "PRINTLN"]).content.token.name ) return "NULL RESULT"
def semantic_analyse(root: ParseTreeNode): assert root.is_non_terminal("p") # this must be program root global_scope = Scope() root.scope = global_scope for child in root.children: child.scope = global_scope if child.is_non_terminal("compound"): _analyse( child, global_scope ) # begin the semantic analyse proper once we find the actual program body
def use_var(self, id_node: ParseTreeNode): identifier = id_node.get_terminal_attribute() token = id_node.content.token if identifier not in self.vars or not self.vars[ identifier].has_been_declared(token): raise ParseError(f"Use of undeclared identifier {identifier}", token.line_num, token.col_num, token.context_line)
def _type_check_function_call(node: ParseTreeNode, procedures, none_return_allowed): id_paren = node.get_child("ID_PAREN") called_procedure = id_paren.content.token.attribute for procedure in procedures: if procedure.get_child( "ID_PAREN").content.token.attribute == called_procedure: if procedure.type == NONE and not none_return_allowed: token = id_paren.content.token raise ParseError( f"Can't assign to procedure that returns none", token.line_num, token.col_num, token.context_line) node.type = procedure.type return token = id_paren.content.token raise ParseError(f"Call to undeclared procedure", token.line_num, token.col_num, token.context_line)
def _analyse(node: ParseTreeNode, scope): node.scope = scope if node.is_non_terminal("function_definition"): _analyse_func_definition(node) elif node.is_non_terminal( "v"): # variable declaration, with optional assignment _analyse_variable_assignment(node, scope, is_declaration=True) elif node.is_non_terminal( "a"): # assignment of an already declared variable _analyse_variable_assignment(node, scope, is_declaration=False) elif node.is_non_terminal("pr") and node.has_child( "GET"): # assign declared variable to user input _analyse_variable_assignment(node, scope, is_declaration=False) elif node.is_terminal("ID"): scope.use_var(node) elif node.children: for child in node.children: _analyse(child, scope)
def oreo_to_tac(self, node: ParseTreeNode): if hasattr(node, "result"): return # this node has already been processed, no need to do it again if node.is_non_terminal("p"): # ignore the top level program declaration, just process the program compound itself self.oreo_to_tac(node.get_child("compound")) return # if statements and while loops are carefully compiled in order, so the right things are in the right labels elif node.is_non_terminal("i"): self._compile_if_statement(node) return elif node.is_non_terminal("w"): self._compile_while_statement(node) return for child in node.children: self.oreo_to_tac(child) if node.is_terminal("NUMBER") or node.is_terminal("STRING"): literal = node.content.token.attribute if node.is_terminal("NUMBER"): literal = int(literal) node.result = NodeResult(literal=literal) elif node.is_terminal("TRUE") or node.is_terminal("FALSE"): bool_literal = TRUE_TAC if node.is_terminal("TRUE") else FALSE_TAC node.result = NodeResult(literal=bool_literal) elif node.is_terminal("ID"): node.result = NodeResult(variable=self._get_variable(node.content.token.attribute, create=True)) elif node.is_in(["term", "factor", "simple_expr", "compare_expr", "bool"]): node.result = self._compile_optional_combiner(node) elif node.is_non_terminal("expression"): node.result = self._compile_optional_combiner(node, specific_combiners=["and_or_b"]) elif node.is_non_terminal("a"): self._compile_assignment(node.get_child("ID"), node.get_child("expression")) elif node.is_non_terminal("v") and node.has_child("var_assign"): self._compile_assignment(node.get_child("ID"), node.get_child("var_assign").get_child("expression")) elif node.is_non_terminal("pr"): node.result = self._compile_print_expression(node) if hasattr(node, "result"): assert isinstance(node.result, NodeResult) else: node.result = "NULL RESULT" # to prevent reprocessing processed nodes which do not have a result
def inherit_node_result(node: ParseTreeNode, child_names: List[str]): child = node.get_a_child(child_names) node.result = child.result
def get_var_type(self, id_node: ParseTreeNode, procedures): identifier = id_node.get_terminal_attribute() return self.vars[identifier].get_type_at_node(id_node, procedures)
def _require_child_type(node: ParseTreeNode, child: str, required_type): _require_type(node.get_child(child), required_type)
def _type_check(node: ParseTreeNode, procedures: List[ParseTreeNode]): # only type check each node once # this is useful because sometimes type checking happens out of order, eg in assignment if hasattr(node, "type"): return # do this first to allow recursive procedures if node.is_non_terminal("function_definition"): procedures.append(node) # type check from the bottom up for child in node.children: _type_check(child, procedures) # there is no nice way to do this because many cases have unique behaviour # so sadly the best simple way to do it is a big old branching if statement if node.is_non_terminal("function_definition"): node.type = node.get_child("function_compound").type elif node.is_non_terminal("function_compound"): _type_check_function_compound(node) elif node.is_non_terminal("return_statement"): _type_check_return_statement(node) elif node.is_non_terminal("arg_type"): node.type = node.children[0].type elif node.is_non_terminal("expression"): _type_check_expression(node) elif node.is_non_terminal("compare_expr"): _type_check_compare_expr(node) elif node.is_non_terminal("simple_expr"): _type_check_simple_expr(node) elif node.is_non_terminal("term"): _type_check_term(node) elif node.is_non_terminal("factor"): _type_check_factor(node, procedures) elif node.is_non_terminal("bool"): _type_check_bool(node) elif node.is_non_terminal("var_assign"): node.type = node.get_child("expression").type elif node.is_non_terminal("comp_e"): _require_child_type(node, "expression", NUM) node.type = BOOL elif node.is_non_terminal("add_sub"): _require_child_type(node, "term", NUM) node.type = NUM elif node.is_non_terminal("mul_div"): _require_child_type(node, "factor", NUM) node.type = NUM elif node.is_terminal("ID"): var_type = node.scope.get_var_type(node, procedures) node.type = var_type elif node.is_terminal("NUMBER") or node.is_terminal("NUM"): node.type = NUM elif node.is_terminal("TRUE") or node.is_terminal( "FALSE") or node.is_terminal("BOOL"): node.type = BOOL # GET gets a string from the user elif node.is_terminal("STRING") or node.is_terminal( "STR") or node.is_terminal("GET"): node.type = STR
def type_check(root: ParseTreeNode): # do not type check the name of the program, just the body _type_check(root.get_child("compound"), [])