Example #1
0
    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)
Example #2
0
    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)
Example #3
0
    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)
Example #4
0
    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)
Example #5
0
 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"
Example #6
0
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
Example #7
0
 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)
Example #8
0
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)
Example #9
0
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)
Example #10
0
    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
Example #11
0
def inherit_node_result(node: ParseTreeNode, child_names: List[str]):
    child = node.get_a_child(child_names)
    node.result = child.result
Example #12
0
 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)
Example #13
0
def _require_child_type(node: ParseTreeNode, child: str, required_type):
    _require_type(node.get_child(child), required_type)
Example #14
0
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
Example #15
0
def type_check(root: ParseTreeNode):
    # do not type check the name of the program, just the body
    _type_check(root.get_child("compound"), [])