Example #1
0
    def recurse_operator(self, node, file_index, function_key):
        """
        Accepts a node and determines the appropriate handler function to use
        then passes the parameters to the correct handler function. Called
        recursively to parse through code lines

        Parameters
        ----------
        node : ast node
            The ast node to be translated
        file_index : int
            Index of the file to write to in the output_files list
        function_key : str
            Key used to find the correct function in the function dictionary

        Returns
        -------
        tuple : (str, [str])
            Tuple with the string representation of the operation and the
            return type in a list of a string

        Raises
        ------
        TranslationNotSupported
            If the python code cannot be directly translated
        """
        node_type = node.__class__
        if node_type is ast.BinOp:
            return self.parse_BinOp(node, file_index, function_key)

        elif node_type is ast.BoolOp:
            return self.parse_BoolOp(node, file_index, function_key)

        elif node_type is ast.UnaryOp:
            return self.parse_UnaryOp(node, file_index, function_key)

        elif node_type is ast.Compare:
            return self.parse_Compare(node, file_index, function_key)

        elif node_type is ast.Call:
            return self.parse_Call(node, file_index, function_key)

        elif node_type is ast.Name:
            # Variable should already exist if we're using it, so we just grab
            # it from the current context
            try:
                return node.id, self.find_var_type(node.id,
                                                   file_index,
                                                   function_key)
            except ppex.VariableNotFound:
                # Can't handle non declared variables being used
                raise ppex.TranslationNotSupported("TODO: Variable used before declaration")

        elif node_type is ast.Constant:
            return self.parse_Constant(node, file_index, function_key)

        else:
            # Anything we don't handle
            raise ppex.TranslationNotSupported()
Example #2
0
    def parse_Compare(self, node, file_index, function_key):
        """
        Handles parsing an ast.Compare node.

        Parameters
        ----------
        node : ast.Compare
            The ast.Compare node to be translated
        file_index : int
            Index of the file to write to in the output_files list
        function_key : str
            Key used to find the correct function in the function dictionary

        Returns
        -------
        return_str : str
            The Compare operation represented as a string
        return_type : list of str
            The return type of the Compare operation

        Raises
        ------
        TranslationNotSupported
            If the python code cannot be directly translated
        """
        # Ensure we can do all types of operations present in code line
        for op in node.ops:
            if op.__class__.__name__ not in PyAnalyzer.comparison_map:
                raise ppex.TranslationNotSupported("TODO: Comparison operation not supported")

        # Comparisons can be chained, so we use the left item as the
        # "last" item to be compared to start the chain
        last_comparator = self.recurse_operator(node.left,
                                                file_index,
                                                function_key)[0]

        return_str = ""

        # Chaining comparisons together with ands
        for index in range(1, len(node.ops)-1):
            comparator = self.recurse_operator(node.comparators[index],
                                               file_index,
                                               function_key)[0]
            return_str += "(" + last_comparator \
                          + PyAnalyzer.comparison_map[node.ops[index-1].__class__.__name__] \
                          + comparator + ") && "
            last_comparator = comparator

        # Add last comparison on the end
        comparator = self.recurse_operator(node.comparators[-1],
                                           file_index,
                                           function_key)[0]

        return_str += "(" + last_comparator + \
                      PyAnalyzer.comparison_map[node.ops[-1].__class__.__name__] \
                      + comparator + ")"

        # All comparisons come back as a bool
        return_type = ["bool"]
        return return_str, return_type
Example #3
0
    def parse_ported_function(self, file_index, function_key, function, args,
                              arg_types):
        """
        Converts a python version of a function to a C++ version

        Parameters
        ----------
        file_index : int
            Index of the file to write to in the output_files list
        function_key : str
            Key used to find the correct function in the function dictionary
        function : str
            Name of the function to convert
        args : list of str
            List containing the arguments represented as strings
        arg_types : list of list of str
            List containing the types of each argument in a list of str

        Returns
        -------
        return_str : str
            The ported function represented as a string
        return_type : list of str
            The return type of the ported function

        Raises
        ------
        TranslationNotSupported
            If the python code cannot be directly translated
        """
        if function == "print":
            return_str = pf.print_translation(args)
            return_type = ["None"]
            self.output_files[file_index].add_include_file("iostream")

        elif function == "sqrt":
            if len(args) > 1:
                raise ppex.TranslationNotSupported("TODO: Can't square more than 1 item")
            return_str = pf.sqrt_translation(args)
            return_type = ["float"]
            self.output_files[file_index].add_include_file("math.h")

        return return_str, return_type
Example #4
0
    def parse_UnaryOp(self, node, file_index, function_key):
        """
        Handles parsing an ast.UnaryOp node.

        Parameters
        ----------
        node : ast.UnaryOp
            The ast.UnaryOp node to be translated
        file_index : int
            Index of the file to write to in the output_files list
        function_key : str
            Key used to find the correct function in the function dictionary

        Returns
        -------
        return_str : str
            The UnaryOp represented as a string
        return_type : list of str
            The return type of the UnaryOp

        Raises
        ------
        TranslationNotSupported
            If the python code cannot be directly translated
        """
        operator = node.op.__class__
        if operator.__name__ not in PyAnalyzer.operator_map:
            raise ppex.TranslationNotSupported("TODO: UnaryOp not supported")

        return_str, return_type = self.recurse_operator(node.operand,
                                                        file_index,
                                                        function_key)

        # Not operation becomes a bool no matter what type it operated on
        if operator is ast.Not:
            return_type = ["bool"]
        else:
            return_type = ["int"]

        return_str = "(" + PyAnalyzer.operator_map[operator.__name__] + return_str + ")"
        return return_str, return_type
Example #5
0
    def find_else_lineno(self, search_index):
        """
        Finds the first else statement starting from the search_index and
        searching upwards in the original python code

        Parameters
        ----------
        search_index : int
            The line number to start searching for the else statement

        Returns
        -------
        search_index : int
            Line number of the else statement
        end_col_offset : int
            Index of the last character of the else in the original python code

        Raises
        ------
        TranslationNotSupported
            If an else is not found
        """
        while search_index > -1:
            # Check line isn't a comment
            if self.raw_lines[search_index].lstrip()[0] == "#":
                search_index -= 1
                continue
            else:
                end_col_offset = self.raw_lines[search_index].find("else:")
                if end_col_offset < 0:
                    raise ppex.TranslationNotSupported("TODO: No corresponding else found")
                else:
                    end_col_offset += 4

                # Line number is 1+index in list
                search_index += 1
                return search_index, end_col_offset
Example #6
0
    def parse_BoolOp(self, node, file_index, function_key):
        """
        Handles parsing an ast.BoolOp node.

        Parameters
        ----------
        node : ast.BoolOp
            The ast.BoolOp node to be translated
        file_index : int
            Index of the file to write to in the output_files list
        function_key : str
            Key used to find the correct function in the function dictionary

        Returns
        -------
        return_str : str
            The BoolOp represented as a string
        return_type : list of str
            The return type of the BoolOp

        Raises
        ------
        TranslationNotSupported
            If the python code cannot be directly translated
        """
        # List of tuples consisting of (string,[type_string])
        compare_nodes = []
        mixed_types = False
        # Multiple nodes can be chained, so we need to go through all of them
        for internal_node in node.values:
            compare_nodes.append(self.recurse_operator(internal_node,
                                                       file_index,
                                                       function_key))

        # This shouldn't be possible normally, but we check to be safe
        if len(compare_nodes) < 2:
            raise ppex.TranslationNotSupported("TODO: Less than 2 items being compared")

        return_str = ""
        ret_var_type = compare_nodes[0][1][0]

        # Go through all but the last one and create a string separated by
        # the C++ version of the python operator
        for compare_node in compare_nodes[:-1]:
            if compare_node[1][0] != ret_var_type:
                mixed_types = True
            return_str += (compare_node[0] +
                           PyAnalyzer.operator_map[node.op.__class__.__name__])

        if compare_nodes[-1][1][0] != ret_var_type:
            mixed_types = True
        return_str += compare_nodes[-1][0]

        # Short circuit operators complicate type determination, so if they
        # aren't all the same type, we'll use auto, otherwise these operators
        # keep they type if all items being compared are the same type
        if mixed_types:
            return_type = ["auto"]
        else:
            return_type = compare_nodes[0][1]

        return_str = "(" + return_str + ")"
        return return_str, return_type
Example #7
0
    def parse_Call(self, node, file_index, function_key):
        """
        Handles parsing an ast.Call node.

        Parameters
        ----------
        node : ast.Call
            The ast.Call node to be translated
        file_index : int
            Index of the file to write to in the output_files list
        function_key : str
            Key used to find the correct function in the function dictionary

        Returns
        -------
        return_str : str
            The call represented as a string
        return_type : list of str
            The return type of the call

        Raises
        ------
        TranslationNotSupported
            If the python code cannot be directly translated
        """
        # Should be a name to have a function call we can parse
        if node.func.__class__ is not ast.Name:
            raise ppex.TranslationNotSupported("TODO: Not a valid call")

        # Get a reference to current function to shorten code width
        func_ref = self.output_files[file_index].functions
        func_name = node.func.id

        # Ensure this is a valid function call we can use
        if func_name not in cvar.CPPVariable.types \
            and func_name not in func_ref \
                and func_name not in self.ported_functions:
            raise ppex.TranslationNotSupported("TODO: Call to function not in scope")

        # We track the types passed in to help update parameter types when
        # functions get called
        arg_types = []
        arg_list = []
        for arg in node.args:
            arg_str, arg_type = self.recurse_operator(arg,
                                                      file_index,
                                                      function_key)
            arg_list.append(arg_str)
            arg_types.append(arg_type)

        # Check if casting or normal function call
        if func_name in cvar.CPPVariable.types:
            # Trim the extra space since we are performing a cast rather than
            # a variable declaration
            if (func_name == "str"):
                return_str = "std::to_string("
                self.output_files[file_index].add_include_file("string")
                return_type = ["str"]
            else:
                return_str = "(" + cvar.CPPVariable.types[func_name][:-1] + ")("
                return_type = [func_name]

        elif func_name in func_ref:
            return_str = func_name + "("

            # Now we try to update the parameter types if applicable
            function = func_ref[func_name]
            for param, passed_type in zip(function.parameters.values(),
                                          arg_types):
                param.py_var_type[0] = self.type_precedence(param.py_var_type,
                                                         passed_type)[0]
            return_type = function.return_type

        elif func_name in self.ported_functions:
            return self.parse_ported_function(file_index, function_key,
                                              func_name, arg_list, arg_types)

        else:
            raise ppex.TranslationNotSupported("TODO: Call to function not in scope")

        # Finish generating function call with parameters inserted
        for arg in arg_list:
            return_str += arg + ", "
        return_str = return_str[:-2] + ")"

        return return_str, return_type