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()
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
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
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
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
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
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