def parse_Expr(self, node, file_index, function_key, indent): """ Handles parsing an ast.Expr node. Parameters ---------- node : ast.Expr The ast.Expr 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 indent : int How much indentation a line should have """ func_ref = self.output_files[file_index].functions[function_key] # Only worrying about docstrings and function calls # Docstrings classified as constants in ast if node.value.__class__ is ast.Constant: if type(node.value.value) is str: # Verify this is a docstring start_chars = self.raw_lines[node.value.lineno-1].strip()[0:3] if start_chars == '"""' or start_chars == "'''": return_str = self.convert_docstring(node.value.value, indent) else: self.parse_unhandled(node, file_index, function_key, indent, "TODO: Constant string not used") return else: self.parse_unhandled(node, file_index, function_key, indent, "TODO: Constant not used") return elif node.value.__class__ is ast.Call: try: return_str, return_type = self.parse_Call(node.value, file_index, function_key) except ppex.TranslationNotSupported as ex: self.parse_unhandled(node, file_index, function_key, indent, ex.reason) return return_str += ";" else: # Any other type doesn't matter as the work it does wouldn't be # saved self.parse_unhandled(node, file_index, function_key, indent, "TODO: Value not assigned or used") return func_ref.lines[node.value.lineno] = cline.CPPCodeLine(node.value.lineno, node.value.end_lineno, node.end_col_offset, indent, return_str)
def parse_unhandled(self, node, file_index, function_key, indent, reason="TODO: Code not directly translatable, manual port required"): """ Handler for any code that cannot be properly translated. This will bring the code verbatim from the original python script and wrap it in a C++ comment and add the reason it wasn't translated one line above it Parameters ---------- node : ast node The ast node that couldn't 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 indent : int How much indentation a line should have reason : str The reason why a line of code wasn't translated """ # Get a reference to the correct function to shorten code width func_ref = self.output_files[file_index].functions[function_key] func_ref.lines[node.lineno] = cline.CPPCodeLine(node.lineno, node.lineno, node.end_col_offset, indent, "/*" + self.raw_lines[node.lineno-1], "", reason) # If the code spanned multiple lines, we need to pull all # of the lines from the original script, not just the first # line for index in range(node.lineno+1, node.end_lineno+1): func_ref.lines[index] = cline.CPPCodeLine(index, index, node.end_col_offset, indent, self.raw_lines[index-1]) # Add the closing comment symbol on the last line func_ref.lines[node.end_lineno].code_str += "*/"
def parse_Return(self, node, file_index, function_key, indent): """ Handles parsing an ast.Return node. Parameters ---------- node : ast.Return The ast.Return 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 indent : int How much indentation a line should have """ func_ref = self.output_files[file_index].functions[function_key] if node.value is None: func_ref.lines[node.lineno] = cline.CPPCodeLine(node.lineno, node.end_lineno, node.end_col_offset, indent, "return;") else: try: return_str, return_type = self.recurse_operator(node.value, file_index, function_key) except ppex.TranslationNotSupported as ex: self.parse_unhandled(node, file_index, function_key, indent, ex.reason) return func_ref.return_type = self.type_precedence(return_type, func_ref.return_type) func_ref.lines[node.lineno] = cline.CPPCodeLine(node.lineno, node.end_lineno, node.end_col_offset, indent, "return " + return_str + ";")
def parse_Continue(self, node, file_index, function_key, indent): """ Handles parsing an ast.Continue node. Parameters ---------- node : ast.Continue The ast.Continue 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 indent : int How much indentation a line should have """ func_ref = self.output_files[file_index].functions[function_key] func_ref.lines[node.lineno] = cline.CPPCodeLine(node.lineno, node.end_lineno, node.end_col_offset, indent, "continue;")
def parse_While(self, node, file_index, function_key, indent): """ Handles parsing an ast.While node. Can be called recursively to handle nested whiles. Parameters ---------- node : ast.While The ast.While 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 indent : int How much indentation a line should have """ func_ref = self.output_files[file_index].functions[function_key] try: test_str = self.recurse_operator(node.test, file_index, function_key)[0] except ppex.TranslationNotSupported as ex: self.parse_unhandled(node, file_index, function_key, indent, ex.reason) return func_ref.lines[node.lineno] = cline.CPPCodeLine(node.lineno, node.end_lineno, node.end_col_offset, indent, "while (" + test_str + ")\n" + indent * cline.CPPCodeLine.tab_delimiter + "{") self.analyze_tree(node.body, file_index, function_key, indent + 1) # Closing the body of the while loop func_ref.lines[node.body[-1].end_lineno].code_str += "\n" \ + indent * cline.CPPCodeLine.tab_delimiter \ + "}"
def ingest_comments(self, raw_lines): """ Pulls comments from the original script, converts them to C++ style comments, then puts them into line dictionaries of their corresponding function so they are included during the output phase Parameters ---------- raw_lines : list of str List of strings containing the original python script line by line """ # First get a dictionary with every existing line of code. That way # we know whether to look for an inline comment or a full line comment for file in self.output_files: all_lines_dict = {} for cfunction in file.functions.values(): # Source: https://stackoverflow.com/questions/38987/how-do-i # -merge-two-dictionaries-in-a-single-expression-in-python # -taking-union-o all_lines_dict = {**all_lines_dict, **cfunction.lines} # Going through all lines in the script we are parsing for index in range(len(raw_lines)): # Line numbers count from 1 while list starts from 0, so we need to offset by 1 if (index + 1) in all_lines_dict: # Looking for inline comment code_line = all_lines_dict[index + 1] comment = raw_lines[index][code_line. end_char_index:].lstrip() # Verify there is a comment present if len(comment) > 0 and comment[0] == "#": # Trim off the comment symbol as it will be changed # to the C++ style comment all_lines_dict[index + 1].comment_str = comment[1:].lstrip() else: # Determine which function the line belongs to for function in file.functions.values(): if function.lineno < index + 1 < function.end_lineno: line = raw_lines[index] comment = line.lstrip() if len(comment) > 0 and comment[0] == "#": # C++ uses '//' to indicate comments instead of '#' comment = line.replace("#", "//", 1) function.lines[index + 1] = cline.CPPCodeLine( index + 1, index + 1, len(line), 0, comment) break else: line = raw_lines[index] comment = line.lstrip() if len(comment) > 0 and comment[0] == "#": # We add an extra indent on code not in a function # since it will go into a function in C++ comment = cline.CPPCodeLine.tab_delimiter + line.replace( "#", "//", 1) file.functions["0"].lines[index + 1] = cline.CPPCodeLine( index + 1, index + 1, len(line), 0, comment) # Sort function line dictionaries so output is in proper order for function in file.functions.values(): sorted_lines = {} for line in sorted(function.lines.keys()): sorted_lines[line] = function.lines[line] function.lines = sorted_lines
def parse_Assign(self, node, file_index, function_key, indent): """ Handles parsing an ast.Assign node. Parameters ---------- node : ast.Assign The ast.Assign 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 indent : int How much indentation a line should have Raises ------ TranslationNotSupported If the python code cannot be directly translated """ function_ref = self.output_files[file_index].functions[function_key] # Won't handle chained assignment if len(node.targets) > 1: self.parse_unhandled(node, file_index, function_key, indent, "TODO: Unable to translate chained assignment") return var_name = node.targets[0].id try: assign_str, assign_type = self.recurse_operator(node.value, file_index, function_key) except ppex.TranslationNotSupported as ex: self.parse_unhandled(node, file_index, function_key, indent, ex.reason) return # Find if name exists in context try: py_var_type = self.find_var_type(var_name, file_index, function_key) # Verify types aren't changing or we aren't losing precision if py_var_type[0] != assign_type[0] \ and (py_var_type[0] != "float" and assign_type[0] != "int"): # Can't do changing types in C++ self.parse_unhandled(node, file_index, function_key, indent, "TODO: Refactor for C++. Variable types " "cannot change or potential loss of " "precision occurred") return else: code_str = var_name + " = " + str(assign_str) + ";" c_code_line = cline.CPPCodeLine(node.lineno, node.end_lineno, node.end_col_offset, indent, code_str) except ppex.VariableNotFound: # Declaration c_var = cvar.CPPVariable(var_name, node.lineno, assign_type) function_ref.variables[var_name] = c_var code_str = var_name + " = " + str(assign_str) + ";" c_code_line = cline.CPPCodeLine(node.lineno, node.end_lineno, node.end_col_offset, indent, code_str) function_ref.lines[node.lineno] = c_code_line
def parse_If(self, node, file_index, function_key, indent, if_str="if"): """ Handles parsing an ast.If node. Can be called recursively to handle nested ifs. Parameters ---------- node : ast.If The ast.If 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 indent : int How much indentation a line should have if_str : str Indicates whether to be an if or else if statement """ func_ref = self.output_files[file_index].functions[function_key] # Parse conditions and add in the code to the current function try: test_str = self.recurse_operator(node.test, file_index, function_key)[0] except ppex.TranslationNotSupported as ex: self.parse_unhandled(node, file_index, function_key, indent, ex.reason) return func_ref.lines[node.lineno] = cline.CPPCodeLine(node.lineno, node.end_lineno, node.end_col_offset, indent, if_str + " (" + test_str + ")\n" + indent*cline.CPPCodeLine.tab_delimiter + "{") self.analyze_tree(node.body, file_index, function_key, indent+1) # Get the last code line and add the closing bracket func_ref.lines[node.body[-1].end_lineno].code_str += "\n" \ + indent*cline.CPPCodeLine.tab_delimiter \ + "}" # Looking for else if or else cases if len(node.orelse) == 1 and node.orelse[0].__class__ is ast.If: # Else if case self.parse_If(node.orelse[0], file_index, function_key, indent, "else if") elif len(node.orelse) > 0: # Else case else_lineno, else_end_col_offset = self.find_else_lineno(node.orelse[0].lineno - 2) func_ref.lines[else_lineno] = cline.CPPCodeLine(else_lineno, else_lineno, else_end_col_offset, indent, "else\n" + indent * cline.CPPCodeLine.tab_delimiter + "{") self.analyze_tree(node.orelse, file_index, function_key, indent + 1) # Get the last code line and add the closing bracket func_ref.lines[node.orelse[-1].end_lineno].code_str += "\n" \ + indent * cline.CPPCodeLine.tab_delimiter \ + "}"