Advertisement
NLinker

C++ function definition parser (Python interop)

Jan 28th, 2021
1,085
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. # py_to_cpp.py
  2.  
  3. #script to read python code in to an AST, parse and replace nodes with corresponding
  4. #c++ code
  5.  
  6. #make relevant imports
  7. import ast
  8. import numpy as np
  9.  
  10. #parser class for function definitions
  11. class FunctionParser(ast.NodeVisitor):
  12.     def visit_FunctionDef(self, node): #visit the function definition node
  13.         #define relevant globals that require access
  14.         global converted_lines, function_body, arg_vars, list_types, class_args
  15.         arg_vars = [] #list of arguments
  16.         args_string = '' #argument string for conversion
  17.         init_arg = [] #store a list of arguments to initialise, done for use in any class definitions
  18.        
  19.         for i in range(0,len(node.args.args)): #iterate over the node arguments
  20.             arg_val = node.args.args[i].arg #for each arg get the arg name
  21.             init_arg.append(arg_val) #append the arg name to the list of args to initialise
  22.        
  23.         #class arguments usually start with self, self is not required for c++
  24.         #a type won't have been defined in the function call for self, therefore if different number of list types to arguments
  25.         #and first argument is self, remove the argument so the list of types and arguments matches up again
  26.         if((len(init_arg) != len(list_types[0])) and init_arg[0] == 'self'):
  27.             init_arg.pop(0)
  28.         else:
  29.             pass
  30.        
  31.         #iterate over the arguments to initialise
  32.         for i in range(0,len(init_arg)):
  33.             arg_type = list_types[0][i] #get the types of the first function's arguments
  34.             full_arg = arg_type + ' ' + init_arg[i] #define a full argument string as the type and name
  35.             arg_vars.append(full_arg) #add the full arg definition to list
  36.             args_string += full_arg + ', ' #add the full arag definition to the arg string
  37.         args_string = args_string[:-2] #remove extra ', ' at the end of the line
  38.         list_types.pop(0) #remove the arg types for the arguments that have just been processed
  39.        
  40.         #if the name of the function is a class initialilser run a special case
  41.         if(node.name == '__init__'):
  42.             class_initialiser = [] #block for class initialisation function
  43.             class_initialiser.append('public:') #mark following class variables as public for initalising the object
  44.             class_args = init_arg #set the class args as a copy of the initialiser args
  45.             for i in node.body: #iterate over the body of the initialiser function
  46.                 line = general_access_node(i) #classify and convert the line of the function
  47.                 splitup = line.split(' ') #split the converted line by space to inspect elements
  48.                 #the following is a messy way to initialise class variables, if it is an initialisation the line will be in the
  49.                 #style std::string name = "name"; which is incorrect formatting due to how these statements are processed elsewhere
  50.                 try:
  51.                     #check if the first argument of splitup (name) is equal to the final element of splitup inside quotations
  52.                     #and without the ; ("name";).
  53.                     if(splitup[1] == ("%s" % splitup[3][:-1]).replace('"','')):
  54.                         #if it is then its a variable declaration, take the converted arg_vars declaration and add a semicolon
  55.                         #it will now be in the form std::string name; (or other appropriate type of variable)
  56.                         string_val = arg_vars[0]+';'
  57.                         class_initialiser.append(string_val) #append the new string to the block
  58.                         arg_vars.pop(0) #remove the arg_var as it has been declared in block
  59.                     else: #if it doesn't match just append the line
  60.                         class_initialiser.append(line)
  61.                 except: #if splitup element access fails just append the line as the above condition will not occur
  62.                     class_initialiser.append(line)
  63.             return class_initialiser #return the initialised class function
  64.         else:
  65.             pass
  66.        
  67.         function_body = [] #define list for the main body of the function
  68.         for i in node.body: #iterte over the nodes in the body of the function
  69.             if(type(i) == ast.Return): #check if the line is a return function
  70.                 line = ReturnParser().visit_Return(i) #visit the return parser function
  71.                 if(line == None): #if return is a void return
  72.                     function_body.append('return') #add a void return to the body
  73.                 else: #if return has arguments
  74.                     return_types = [] #make a list of the types of values being returned
  75.                     for j in range(0,len(line)):  #iterate over the return values listed
  76.                         for i in reversed(range(0,len(function_body))): #iterate backwards over the body of the function (find the latest definitions of the variables)
  77.                             declaration_check = ' %s = ' % line[j] #check for a definition of the variable
  78.                             if(declaration_check not in function_body[i]): #if there is not a definition of the variable on this line of the function body skip it
  79.                                 pass
  80.                             else: #if a definition is found isolate they type by taking the first word and add it to the return types list
  81.                                 return_types.append(function_body[i].split(' ')[0])
  82.                     #if there is only one return value a normal return can be used
  83.                     if(len(return_types) == 1):
  84.                         #add the return and the value to the function body
  85.                         function_body.append('return %s;' % line)
  86.                     else: #if multiple values are required generate a structure to return
  87.                         struct_string = 'struct result {' #initialise structure string
  88.                         for i in range(0,len(line)): #iterate over the number of arguments
  89.                             #add (value_type dummy_return_[number];) to the structure string  
  90.                             struct_string += return_types[i] + ' dummy_return_%s; ' % str(i)
  91.                         #remove the extra space, close the struct bracket and end statement for struct definition
  92.                         struct_string = struct_string[:-1] + '};'
  93.                         function_body.append(struct_string) #add the struct definition to the function body
  94.                         return_string = 'return result {' #create string for function returns using the structure just defined
  95.                         for i in range(0,len(line)): #iterate over the return arguments
  96.                             return_string += line[i] + ', ' #add the arguments to the return string
  97.                         return_string = return_string[:-2] + '};' #remove the extra ', ' and close bracket and end return statement
  98.                         function_body.append(return_string) #add the return string to the function body
  99.             else: #if the node is not a return determine the type and convert it then add to function body
  100.                 function_body.append(general_access_node(i))
  101.         if('return' in function_body[-1]): #check if the function was ended with a return
  102.             pass
  103.         else: #if no return add one
  104.             function_body.append('return;')
  105.         if(node.name == 'main'): #add a catch to prevent duplicate main functions within the converted script
  106.             raise NameError('A function named "main" cannot be used within a C++ script as it is the default insertion point for the code, please rename this function in your script, this script will fill in a C++ main function automatically')
  107.         else:
  108.             pass
  109.        
  110.         function = [] #block for whole function
  111.         #define the c++ function definition with the function name and the arguments string
  112.         function_def = 'auto %s (%s) {' % (node.name, args_string)
  113.         function.append(function_def) #add the function definition line
  114.         function.append(function_body) #add the function body
  115.         function.append('}') #close the open function brace
  116.         arg_vars = [] #reset arg_vars as it is global
  117.         function_body = [] #reset function body as it is global
  118.         return function #return whole function
  119.  
  120. #define parser for class definitions
  121. class ClassDefParser(ast.NodeVisitor):
  122.     def visit_ClassDef(self,node): #visit class definition node
  123.         global class_args #access to relevant globals
  124.         class_block = [] #block of lines of converted class
  125.         class_name = node.name #get the name of the class
  126.         class_dec = 'class %s {' % class_name #make a converted class statement
  127.         class_block.append(class_dec) #append the class stement to the block
  128.        
  129.         class_body = [] #make list of body of class statement
  130.         for i in node.body: #iterate over the nodes in the body
  131.             line = general_access_node(i) #convert the line of nodes and return
  132.             class_body.append(line) #append the converted line to the body
  133.        
  134.         class_block.append(class_body) #append the body to the block
  135.         class_block.append('};') #close off the class definition
  136.        
  137.         #@todo these properties
  138.         #print(node.bases)
  139.         #print(node.keywords)
  140.         #print(node.decorator_list)
  141.         return class_block #return the class block
  142.  
  143. #input classifying and converting function
  144. def input_convert(name,data,type_var): #pass args of the variable, input arg data and the type of variable (pre-determined)
  145.     val_assign = data #set val assign as data
  146.     name_assign = name #set name assign as name
  147.     converted_input = [] #list of converted input lines
  148.     args = val_assign[1] #store the args of the input string
  149.     outstring = args[0] #argument of the input string (line to output to prompt an input)
  150.     outstring = string_or_var(outstring) #check if the outstring is a variable or string question
  151.     outline = 'std::cout << ' + outstring + ';' #output the outstring
  152.     converted_input.append(outline) #store the output line
  153.     var = name_assign #store name of variable under var
  154.     set_var = type_var + ' ' + var +';' #declare the input variable using the previously found type
  155.     converted_input.append(set_var) #append the declaration to the input lines
  156.     if(type_var=='std::string'): #if the type of input is a string
  157.         input_string = 'std::getline (std::cin, %s);' % var #format a getline command to avoid whitespace breaks as this is probably unintended
  158.         converted_input.append(input_string) #append the getline command to the input lines
  159.     else: #if some other var type like float or int
  160.         input_string = 'std::cin >> %s;' % var #use a standard cin command
  161.         converted_input.append(input_string) #append the input command
  162.         clear_in_buffer = 'std::cin.get();' #add a line to clear the input buffer to remove trailing \n for future input
  163.         converted_input.append(clear_in_buffer) #append the clear buffer command
  164.     end_line = 'std::cout << std::endl;' #append command to end line after inputs so next line isn't printed on the same line
  165.     converted_input.append(end_line) #append the end line command to the input conversion
  166.     return converted_input #return the input conversion
  167.  
  168. #parser class for assign statements, anything with an =
  169. class AssignParser(ast.NodeVisitor):
  170.     def visit_Assign(self,node): #function to visit the assign node
  171.         global converted_lines, arg_vars, class_vars_for_call, called_objs, list_spiel #access to required globals
  172.         for name in node.targets: #iterate over the node targets
  173.             name_assign = general_access_node(name) #get the name, this is the variable the value is assigned to
  174.         if(type(node.value) == ast.BinOp): #check for binary operators in the statement
  175.             val_assign = BinOpParser().visit_BinOp(node.value) #send the node value to the binary operator parser, this will return the arguments swapping out ast operators for c++ operators
  176.             flatten = [] #list for converting any sublists to one 1D array
  177.             for i in val_assign: #iterate over the values
  178.                 if isinstance(i,list): #if one of the values is a list
  179.                     for j in i: #iterate the values in that list and append to the flattened array
  180.                         flatten.append(j)
  181.                 else: #if not a list just append the values directly
  182.                     flatten.append(i)
  183.             if(flatten!=[]): #if anything was added to the flatten list set the list as the value for the assign
  184.                 val_assign = flatten
  185.             else:
  186.                 pass
  187.             val_assign = ['BinOp',val_assign] #specify that this was a BinOp assignment
  188.         else: #if it wasn't a bin op find the type of node and convert the value to appropriate formatting
  189.             val_assign = general_access_node(node.value)
  190.        
  191.         try:
  192.             if(name_assign==("%s" % val_assign)): #if name_assign assign and val assign are the same
  193.                 class_vars_for_call.append(val_assign) #store the val in a list of class variables
  194.             else:
  195.                 pass
  196.         except:
  197.             pass
  198.  
  199.         type_check = type(val_assign) #find the type data the value assign is
  200.         if(type_check == tuple and val_assign[0] == 'open'): #this condition will be met if the user attempts to open a file
  201.             #val_assign will be in format (open,[filename,r/w/a])
  202.             file_conversion = [] #make a list for file operation conversion
  203.             args = val_assign[1] #args of the
  204.             file_name = args[0] #get the file name as first arg
  205.             open_type = args[1] #get the type of file operation as second arg
  206.             file_declare = 'std::fstream %s;' % name_assign #make declaration of file stream
  207.             file_conversion.append(file_declare) #appedn declaration to file conversion
  208.             #convert appropriate operation type to c++ equivalent
  209.             if('r' in open_type):
  210.                 open_type = 'std::ios::in'
  211.             elif('w' in open_type):
  212.                 open_type = 'std::ios::out'
  213.             elif(open_type == 'a'):
  214.                 open_type = 'std::ios::app'  
  215.                
  216.             file_name = file_name.replace('\\',"/") #replace backslash with forward to prevent issues in file name
  217.             open_line = '%s.open("%s",%s);' % (name_assign,file_name,open_type) #declare line to open file with appropriate type
  218.             file_conversion.append(open_line) #append file opening statement to file conversion block
  219.             return file_conversion #return file conversion block
  220.         #this condition will be met if someone declares an empty list
  221.         elif(val_assign == []):
  222.             if(list_spiel == True): #print this spiel of information if it is the first time this warning has come up
  223.                 print('\nAn empty list was detected in your python script, this is a problem for the conversion.')
  224.                 print('The C++ equivalent of python lists need to have the data type being entered declared in advance.')
  225.                 list_spiel = False
  226.             else:
  227.                 pass
  228.            
  229.             appropriate_answer = False #inform user how to classify and enter their list types
  230.             print('\nEmpty list detected called "%s", please enter a data type for the list. Accepted data types are: integer, float, string' % name_assign)
  231.             print('If list will contain more lists please enter: list(type_of_data_in_sublist) and so on if sub_list type is another list, see "help" for an example.')
  232.             while(appropriate_answer==False): #keep getting an input until an appropriate one is entered
  233.                 data_type = input('Please enter a type listed above or "help" for more information: ')
  234.                
  235.                 if(data_type.lower() == 'help'): #if user asking for help print examples of each type
  236.                     print('For an integer list type, the list could look like: [1,2,3,4,5]')
  237.                     print('For a float list type, the list could look like [1.1,2.2,3.3,4.4,5.5]')
  238.                     print('For a string list type, the list could look like ["Apples","Oranges","Bananas","Pears"]')
  239.                     print('For a list of lists, the list could look like [ [[1,1,1],[2,2,2]] , [[3,3,3],[4,4,4]] ]. In this case you would need to enter: list(list(integer)).')
  240.                     print('This is because you enter list one less time than the nest level (nest_level = consecutive "[" at the start) and the lowest level data is of type integer')
  241.                 elif(data_type.lower() == 'integer'): #format list of integers
  242.                     type_check = 'std::vector<int>'
  243.                     val_assign = '{}'
  244.                     appropriate_answer = True
  245.                 elif(data_type.lower() == 'float'): #format list of floats
  246.                     type_check = 'std::vector<float>'
  247.                     val_assign = '{}'
  248.                     appropriate_answer = True
  249.                 elif(data_type.lower() == 'string'): #format list of strings
  250.                     type_check = 'std::vector<std::string>'
  251.                     val_assign = '{}'
  252.                     appropriate_answer = True
  253.                 elif('list' in data_type.lower()): #format list of lists
  254.                     converted_string_lists = data_type.replace('list','std::vector').replace('(','<').replace(')','>')
  255.                     converted_string_lists = converted_string_lists.replace('integer','int').replace('string','std::string')
  256.                     type_check = 'std::vector<%s>' % converted_string_lists
  257.                     val_assign = str(val_assign).replace('[','{').replace(']','}')
  258.                     if('int' in converted_string_lists or 'float' in converted_string_lists or 'std::string' in converted_string_lists):
  259.                         appropriate_answer = True
  260.                     else:
  261.                         print('Invalid input, please try again')
  262.                 else:
  263.                     print('Invalid input, please try again')
  264.         #this condition will be met if line is assigning an input to a variable
  265.         elif(type_check == tuple and val_assign[0] == 'input'):
  266.             converted = input_convert(name_assign,val_assign,'std::string') #convert the input using the function above passing type as string as input was not formatted with int(input()) or float(input())
  267.             return converted #return the conversion
  268.         #this condition met if input wrapped in a type command of int or float or just a standard int/float command
  269.         elif(type_check == tuple and (val_assign[0] == 'int' or val_assign[0] == 'float')):
  270.             if(val_assign[1][0][0] == 'input'): #if an input command wrapped by the function
  271.                 converted = input_convert(name_assign,val_assign[1][0],val_assign[0]) #convert input command using var type of the wrapping function
  272.                 return converted #return the converted input
  273.             else: #if it's not an input raise a TypeError as it is not handled yet
  274.                 #@todo normal int() float() list() str() commands
  275.                 raise TypeError('Conversion of arg type not handled %s' % val_assign)
  276.         #if the val_assign is a call to create an object this condition will be met, this method is a bit messy and could potentially do with reworking
  277.         elif(type_check == tuple and any(('class %s {' % val_assign[0]) in x for x in converted_lines)):
  278.             #print(val_assign[0], name_assign, val_assign[1])
  279.             #val_assign will have format (Class_Name,[init_arg,init_arg,...])
  280.             obj_declaration = [] #make list for object declaration
  281.             secondary_class_store = [] #secondary list of class
  282.             assign_obj = '%s %s;' % (val_assign[0],name_assign) #definition of creating object conversion
  283.             obj_declaration.append(assign_obj) #add object declaration to body
  284.             args_obj = val_assign[1] #isolate arguments of the object declaration
  285.             count = 0 #iterator for removing used object args
  286.             recall = False #flag for if this is the second time an object of the same type is being created
  287.             for i in range(0,len(called_objs)): #iterate over list of previously called classes
  288.                 if(val_assign[0] == called_objs[i][0]): #check if this object is same type as existing
  289.                     recall = True #mark that this is a recall
  290.                     break #stop iterating
  291.                 else:
  292.                     pass
  293.             if(recall==False): #if this is the first time this class has been called
  294.                 secondary_class_store.append(val_assign[0]) #append the type to secondary list tracking classes called
  295.                 for i in range(0,len(args_obj)): #iterate over the object args
  296.                     if(type(args_obj[i]) == str): #if type of arg is a string
  297.                         args_obj[i] = string_or_var(args_obj[i]) #check if was a string or variable, replace it with variable if variable
  298.                     else:
  299.                         pass
  300.                     secondary_class_store.append(class_vars_for_call[i]) #store the class variable name
  301.                     converted_line = '%s.%s = %s;' % (name_assign,class_vars_for_call[i],args_obj[i]) #initialise class parameters with values
  302.                     count+=1 #increase count of variables from class_vars_for_call used
  303.                     obj_declaration.append(converted_line) #append the converted line to the declaration block of the object
  304.                 called_objs.append(secondary_class_store) #append previously called objects with the secondary list of class info
  305.                 class_vars_for_call = class_vars_for_call[count:] #omit the used up variables from the class_vars_for_call list
  306.             else: #if the class has been called before
  307.                 for j in range(0,len(called_objs)): #iterate over the previously called objects list
  308.                     if(called_objs[j][0] == val_assign[0]): #find the match case for this call
  309.                         for k in range(1,len(called_objs[j])): #iterate over the arguments in that match call
  310.                             #args_obj will take values one less than the corresponding stored argument as the first argument in a called_objs element will be the name of the class
  311.                             #therefore called_objs[j] = ['Class_name',arg1,arg2,...], so for each k the corresponding arg to use is k-1
  312.                             if(type(args_obj[k-1]) == str): #if type of arg is a string
  313.                                 args_obj[k-1] = string_or_var(args_obj[k-1]) #check if was a string or variable, replace it with variable if variable
  314.                             else:
  315.                                 pass
  316.                             converted_line = '%s.%s = %s;' % (name_assign,called_objs[j][k],args_obj[k-1]) #initialise class parameters with values
  317.                             obj_declaration.append(converted_line) #append the converted line to the object declaration
  318.                         break #break loop as relevant match found
  319.                     else:
  320.                         pass
  321.             return obj_declaration #return the object declaration
  322.         #if the val_assign is a return from the subscript parser this condition will be met
  323.         elif(type_check == tuple and val_assign[0] == 'subscript'):
  324.             #val_assign here will be ('subscript',['index',list_name,index_value])
  325.             args = val_assign[1] #arguments of the assign statement stored
  326.             list_name = args[1] #the name of the list is the argument 1
  327.             type_script = args[0] #the type of subscripting (index/slice) stored
  328.             if(type_script == 'index'): #if it is index subscripting
  329.                 subscript = list_name + '[%s]' % args[2] #the subscript formtating is list_name[index_value]
  330.             elif(type_script == 'slice'): #if it is slice subscripting
  331.                 subscript = list_name + '[%s:%s]' % (args[2],args[3]) #the subscript formatting is list_name[lower_index:upper_index]
  332.             else: #raise type error as other types are not handled yet
  333.                 raise TypeError('Subscript type not yet handled: %s' % type_script)
  334.            
  335.             found = False #flag for if a declaration of list has been found
  336.             #multiple iterating methods will follow this to check different places for declaration of the list
  337.             #check the converted lines so far in reverse order to get the most recent declaration of the list
  338.             for i in reversed(range(0,len(converted_lines))):
  339.                 find_def = '%s = ' %  list_name #check for declaration of the list
  340.                 if(find_def in converted_lines[i]): #check if the definition is on the current converted line
  341.                     type_check = converted_lines[i].split(' ')[0] #if declaration match found isolate the type of the list
  342.                     found = True #flag a declaration has been found
  343.                 else:
  344.                     pass
  345.             #comparison to be made for if the list was declared in a function definition where the function has not yet been added to converted lines
  346.             function_var = ' %s' % list_name
  347.             if(arg_vars != [] and found == False): #if the list of arguments for active function is not empty and a match was not yet found
  348.                 for i in range(0,len(arg_vars)): #iteratae over the arguments in the function definition
  349.                     if(function_var not in arg_vars[i]):  #if no match for current argument pass
  350.                         pass
  351.                     else: #if match found isolate the type from the argument as the type for the subscript
  352.                         type_check = arg_vars[i].split(' ')[0]
  353.                         found = True #flag a match was found
  354.             else:
  355.                 pass
  356.             #comparison to be made for if the list was declared in the body of the function currently being converted as it has not yet been added to converted lines
  357.             if(function_body != [] and found == False):
  358.                 #iterate backwards over the lines in the function body to get most recent declaration
  359.                 for i in reversed(range(0,len(function_body))):
  360.                     declaration_check = '%s = ' % list_name #expected declaration style of the list
  361.                     #if not match in the line do nothing
  362.                     if(declaration_check not in converted_lines[i]):
  363.                         pass
  364.                     else: #if there is a match isolate the type from the declaration
  365.                         type_check = converted_lines[i].split(' ')[0]
  366.                         found = True #flag as found
  367.             #default to an auto type if no match is found
  368.             if(found == False):
  369.                 type_check = 'auto'
  370.             else:
  371.                 #list type will return something like vector<float>, the value from the subscript will therefore be a float
  372.                 #need to isolate what is inside the first level of angle brackets
  373.                 inner_level = type_check.split('std::vector<',1)[1] #remove the first lot of vector definition
  374.                 mirrored = inner_level[::-1].replace('>','',1) #remove the final angle bracket to completely isolate the inner level
  375.                 isolated_inner = mirrored[::-1] #re-mirror to get back original value
  376.                 type_check = isolated_inner #type is now this isolted inner value
  377.            
  378.             val_assign = subscript #value is the formatted subscript
  379.        
  380.         #check if the name is a tuple, this conidition is met when attempting to set a list subscript to a certain value i.e. list[index] = 3
  381.         elif(type(name_assign) == tuple):
  382.             #name_assign will have the style ('subscript',['index',list_name,index_val])
  383.             args = name_assign[1] #store arguments as the first element
  384.             list_name = args[1] #name of the list is first argument element
  385.             type_script = args[0] #store the type of subscripting
  386.             if(type_script == 'index'): #if index type of subscripting format subscript as list_name[index_value]
  387.                 subscript = list_name + '[%s]' % args[2]
  388.             elif(type_script == 'slice'): #if slice type of subscripting format subscript as list_name[lower_index:upper_index]
  389.                 subscript = list_name + '[%s:%s]' % (args[2],args[3])
  390.             else: #if not one of these two raise a type error as it's not handled yet
  391.                 raise TypeError('Subscript type not yet handled: %s' % type_script)
  392.             #make converted line in the style list_name[index] = val;
  393.             converted_line = subscript + ' = ' + str(val_assign) +';'
  394.             return converted_line #return converted line as it's in different style to other assigns
  395.         #if the type check is a tuple this is a function call return stored to a variable
  396.         elif(type_check == tuple):
  397.             #val assign will be equal to ('func_name',[func_args])
  398.             func_name = val_assign[0] #store the name of the function
  399.             val_assign = val_assign[1] #store the args of the function
  400.             type_check = 'auto' #default to auto type returning
  401.             args = val_assign #set args as the stored values
  402.             for j in range(0,len(args)): #iterate over the args checking if string or variable
  403.                 args[j] = string_or_var(args[j]) #stays same if variable overwrites to be in "" if string
  404.                
  405.             out_args = '' #set arguments string
  406.             for i in range(0,len(args)): #iterate over the arguments
  407.                 out_args += args[i] + ', ' #add the argument and ', ' to make comma separated string
  408.             out_args = out_args[:-2] #remove the extra ', '
  409.             #call formatted as func_name(func_args)
  410.             val_assign = func_name + '(' + out_args + ')'
  411.        
  412.         #if val_assign is a list and a return of a binary operator string
  413.         elif(type_check == list and val_assign[0] == 'BinOp'):
  414.             #val_assign will be equal to ('BinOp',[list of values and operators])
  415.             op_string = val_assign[1] #store list of arguments
  416.             eq_string = '' #define the string of the equation
  417.             for i in op_string: #iterate over the arguments
  418.                 eq_string += str(i) + ' ' #add the argument to the existing string
  419.             eq_string = eq_string[:-1] #remove the extra space at the end of the argument string
  420.             val_assign = eq_string #set val_assign to this formatted string
  421.            
  422.             #a BinOp string could be for a concatenation of a string instead of maths
  423.             #so attempt to determine the type of the first variable in the equation
  424.             found = False #flag for no match
  425.             #iterate backwards over converted lines for most recent definition
  426.             for i in reversed(range(0,len(converted_lines))):
  427.                 find_def = ' %s = ' %  op_string[0] #define the string to search for
  428.                 if(find_def in converted_lines[i]): #if a match on this line
  429.                     type_check = converted_lines[i].split(' ')[0] #isolate the type from the first word of the line
  430.                     found = True #flag that a matach has been found
  431.                 else:
  432.                     pass
  433.             function_var = ' %s' % op_string[0] #string to search for match in function arguments
  434.             #only do this search if the line is in the body of a function that has not yet been completed
  435.             if(arg_vars != [] and found == False):
  436.                 for i in range(0,len(arg_vars)): #iterate over the arguments of the function
  437.                     if(function_var not in arg_vars[i]): #if there is no match for this argument pass
  438.                         pass
  439.                     else: #if there is a match isolate the type from the first word of the argument declaration
  440.                         type_check = arg_vars[i].split(' ')[0]
  441.                         found = True #flag that a match has been found
  442.             else:
  443.                 pass
  444.             #if a function body is currently in conversion and not yet appended to converted lines iterate over it to find match
  445.             if(function_body != [] and found == False):
  446.                 #iterate backwards through body to get most recent declaration
  447.                 for i in reversed(range(0,len(function_body))):
  448.                     declaration_check = '%s = ' % op_string[0] #string to check for in function body
  449.                     if(declaration_check not in converted_lines[i]): #if no match this line do nothing
  450.                         pass
  451.                     else: #if match was found isolate the type as first word of line
  452.                         type_check = converted_lines[i].split(' ')[0]
  453.                         found = True #flag as found
  454.             else:
  455.                 pass
  456.                
  457.             if(found == False): #if no match was found default to an auto type
  458.                 type_check = 'auto'
  459.             else: #if match don't overwrite
  460.                 pass
  461.         #if the type of val_assign is a list then it is a list declaration
  462.         elif(type_check == list):
  463.             #val_assign could be 1D list such as [2,3,4] etc or could be list of lists
  464.             inside_level = val_assign[0] #get the first element of the list to test if it is also a list for declaration purposes
  465.             nest_level = 1 #indicate the level of nesting of the lists (this assumes there is one more list inside at leas, nest level is reduced by one at the end of this block to compensate for overcounting)
  466.             while(type(inside_level) == list): #if the inside level is another list
  467.                 inside_level = inside_level[0] #take another inside level
  468.                 nest_level+=1 #increase the nesting count of the list
  469.             nest_level-=1 #remove one to compensate for overcounting the nesting level
  470.             #for as many lists are nested repeat std::vector<, for example the list [[2,3,4],[5,6,7]]
  471.             #here would get a type check of std::vector<std::vector<int>>
  472.             type_check = 'std::vector<'*(nest_level+1)+str(type(inside_level))+'>'*(nest_level+1)
  473.             val_assign = str(val_assign).replace('[','{').replace(']','}') #convert list formatting to vector formatting
  474.         #if val_assign is a string
  475.         elif(type_check == str):
  476.             #check to ensure it is not a bool value
  477.             if(val_assign == 'true' or val_assign == 'false'):
  478.                 type_check = 'bool' #set type as bool
  479.             else:
  480.                 #val_assign could be equal to 'Hello' for example
  481.                 val_assign = '"%s"' % val_assign #add speech marks around the value to allow string formatting
  482.                 type_check = 'std::string' #specify the type to declare as std::string
  483.         #for any other standard type, for example val_assign = 3.3, type_check = <class 'float'>
  484.         else:
  485.             pass
  486.         #define the converted declaration as the type check (if a standard one then remove the <class ' and '> from the string of the type)
  487.         #then the variable name and the value assigned to it, e.g. float test = 7.9
  488.         converted_line = str(type_check).replace("<class '",'').replace("'>","") + ' %s = %s;' % (name_assign,val_assign)
  489.  
  490.         return converted_line #return the converted line
  491.  
  492. #define a parser for number nodes
  493. class NumParser(ast.NodeVisitor):
  494.     def visit_Num(self,node): #define function to visit the number node
  495.         return node.n #return the number from the node
  496.  
  497. #define a parser for a unary operator node
  498. class UnaryOpParser(ast.NodeVisitor):
  499.     def visit_UnaryOp(self,node): #visit the unary operator node
  500.         if(type(node.op) == ast.USub): #if the operator is a '-' to make a negative number
  501.             num = NumParser().visit_Num(node.operand) #get the number (this will be the number without - operator)
  502.             num_with_operator = -1*num #make the number negative
  503.             return num_with_operator #return the negative number
  504.         elif(type(node.op) == ast.UAdd): #if the operator is a '+' to make a positive number
  505.             num = NumParser().visit_Num(node.operand) #get the number
  506.             num_with_operator = np.abs(num) #take the absolute of it to make it positive
  507.             return num_with_operator #return the number
  508.  
  509. #define a parser for string nodes
  510. class StrParser(ast.NodeVisitor):
  511.     def visit_Str(self,node): #visit the string node
  512.         return node.s #return the string value
  513.  
  514. #define a parser for list nodes
  515. class ListParser(ast.NodeVisitor):
  516.     def visit_List(self,node): #visit the list node
  517.         list_vals = [] #define list of the values
  518.         for i in node.elts: #for each argument of the list
  519.             #find the type and make any necessary conversions then append to the list values
  520.             #if there is a nested list then the type will revisit this parser, e.g. list_Vals = [] to start
  521.             #then a sub list vals is generated and values appended to it then return that sub list to
  522.             #append to the original list_vals, e.g. [[2,3,4]]
  523.             list_vals.append(general_access_node(i))
  524.        
  525.         return list_vals #return the completed list of values
  526.  
  527. #define a parser for subscript nodes (taking indices or slices of lists)
  528. class SubscriptParser(ast.NodeVisitor):
  529.     def visit_Subscript(self,node): #visit the subscript node
  530.         list_slice = [] #list of parameters of the subscript to format
  531.         name = node.value.id #the variable name the list has
  532.         if(type(node.slice) == ast.Index): #if the type of subscripting is taking an index from the list
  533.             index = general_access_node(node.slice.value) #get the value of the index, could be number or letter if in a loop process
  534.             list_slice.append('index') #mark this was an index process for later conversion
  535.             list_slice.append(name) #add the name to the parameters
  536.             list_slice.append(index) #add the index value to the parameters
  537.  
  538.         elif(type(node.slice) == ast.Slice): #if type of subscripting is a slice
  539.             lower_index = general_access_node(node.slice.lower) #get the value of the lower index of the list
  540.             upper_index = general_access_node(node.slice.upper) #get the value of the upper index of the list
  541.             list_slice.append('slice') #mark this was a slice for ater conversion
  542.             list_slice.append(name) #add the name to the parameters
  543.             list_slice.append(lower_index) #add the lower index to the parameters
  544.             list_slice.append(upper_index) #add the upper index to the parameters
  545.  
  546.         else: #need to do extslice type here later
  547.             pass #@todo ExtSlice
  548.        
  549.         return 'subscript',list_slice #return a marking that this is a subscript node and the values
  550.  
  551. #parser for binary operator nodes
  552. class BinOpParser(ast.NodeVisitor):
  553.     def visit_BinOp(self,node): #visit the binary operator node
  554.         vals = [] #list for values either side of binary operator
  555.         left_val = general_access_node(node.left) #determine the type and get the value of arguments left of the operator
  556.         right_val = general_access_node(node.right) #determine the type and get the value of arguments right of the operator
  557.        
  558.         ast_ops = [ast.Add,ast.Sub,ast.Div,ast.Mult] #list of types of ast operators
  559.         #@todo handle more operators
  560.         c_ops = ['+','-','/','*'] #corresponding list  of C++ operators
  561.         operator = node.op #get operator between the left and right vals
  562.         try: #attempt to find the operator type from the ast operator list
  563.             op_index = ast_ops.index(type(operator))
  564.             operator = c_ops[op_index] #get the corresponding C++ operator
  565.         except: #if no index found then raise a type error to flag that it needs handling
  566.             raise TypeError('Binary operator type not handled yet: %s' % operator)
  567.         vals.append(left_val) #append the left value
  568.         vals.append(operator) #append the new C++ operator
  569.         vals.append(right_val) #append the right value
  570.         return vals #return the list of values
  571.  
  572. #define a parser for expression nodes
  573. class ExprParser(ast.NodeVisitor):
  574.     def visit_Expr(self,node): #visit the expression node
  575.         global converted_lines, function_body, arg_vars #allow access to relevant globals
  576.         line = general_access_node(node.value) #determine type and do any conversions for the argument of the expression
  577.         #example line = ('print',[a,b,c])
  578.         function = line[0] #the function of the expression
  579.         #handle different inbuilt functions to convert to C++ versions
  580.         #@todo more of these inbuilts
  581.         if(function == 'print'): #if the expression is a print statement
  582.             function = 'std::cout << ' #replace the function with the std::cout function which will have at least one argument
  583.             args = line[1] #store the arguments of the function
  584.             #iterate over the function arguments, this is to check if the argument is a variable or a string
  585.             for j in range(0,len(args)):
  586.                 #print(args)
  587.                 args[j] = string_or_var(args[j])
  588.  
  589.             out_args = '' #string of output arguments
  590.             for i in range(0,len(args)): #iterate over the number of arguments
  591.                 out_args += args[i] + ' << ' #add the argument and ' << ' to the output string
  592.             out_args += 'std::endl' #add an endline to the end of the string
  593.            
  594.             converted_line = function + out_args + ';' #make the converted line std::cout << arg1 << arg2 ... << std::endl;
  595.        
  596.         elif('.' in function): #check if the function is an attribute
  597.             #this means it is an attribute that should have already been resolved
  598.             #an example could be line = ('g.append(',[args_list])
  599.             args = line[1] #store the args as the first element
  600.             for j in range(0,len(args)): #iterate over the arguments
  601.                 if(type(args[j]) == str):
  602.                     args[j] = string_or_var(args[j])
  603.  
  604.                 elif(type(args[j]) == list): #if the argument type is a list
  605.                     args[j] = str(args[j]).replace('[','{').replace(']','}') #convert argument to vector notation
  606.                 else:
  607.                     pass
  608.             args_string = '' #define a blank string to make string of arguments
  609.             for i in range(0,len(args)): #iterate over the arguments
  610.                 args_string += str(args[i]) + ', ' #add the argument and ', ' to the arguments string
  611.             args_string = args_string[:-2] #remoev the extra ', ' from the end
  612.             converted_line = '%s%s);' % (function,args_string) #complete the converted line to gie e.g. g.push_back(9.9)
  613.         else:
  614.             #if made it this far the expression is treated as a function call
  615.             #example line = ('function_name',[function_args])
  616.             args = line[1] #store the arugments of the function call
  617.             for j in range(0,len(args)): #iterate over the arguments
  618.                 args[j] = string_or_var(args[j])
  619.                
  620.             out_args = '' #as before format final arguments string as comma separated
  621.             for i in range(0,len(args)):
  622.                 out_args += args[i] + ', '
  623.             out_args = out_args[:-2]
  624.             #define the converted line as function_name(funtion_args);
  625.             converted_line = function + '(' + out_args + ')' + ';'
  626.  
  627.         return converted_line #return the converted line  
  628.  
  629. #define parser to handle function call nodes
  630. class CallParser2(ast.NodeVisitor):
  631.     def visit_Call(self,node): #visit function call node
  632.         func_type = general_access_node(node.func) #call to get the value of the name of the function being called
  633.         args_list = [] #list for arguments of tbhe function
  634.         for i in range(0,len(node.args)): #iterate over the arguments of the function
  635.             #print(node.args[i])
  636.             args_list.append(general_access_node(node.args[i])) #classify and extract the value of the arguments of the function
  637.         if(func_type == 'len'): #special case for the len function as this is an attribute of .size() in C++, other special conditions can be coded in here
  638.             converted_func = args_list[0] + '.size()' #e.g. if it was len(a) change to a.size()
  639.             return converted_func #return the converted function
  640.         else:
  641.             pass
  642.         return func_type, args_list #if special cases not met return the type of function and arguments list
  643.  
  644. #define parser to handle return nodes
  645. class ReturnParser(ast.NodeVisitor):
  646.     def visit_Return(self,node): #visit return node
  647.         if(type(node.value) == None): #if it is a void return with no values return None
  648.             return node.value
  649.         else: #if it has values get the types and values it is meant to returning and return them
  650.             args_list = general_access_node(node.value)
  651.             return args_list
  652.  
  653. #define parser to handle tuple nodes
  654. class TupleParser(ast.NodeVisitor):
  655.     def visit_Tuple(self,node): #visit tuple node
  656.         args_list = [] #define list of arguments
  657.         for i in range(0,len(node.elts)): #iterate over the values in the tuple
  658.             args_list.append(general_access_node(node.elts[i])) #get the type and subsequant value of each argument in the tuple
  659.         return args_list #return a list of arguments
  660.  
  661. #define parser to handle if statement nodes
  662. class IfParser(ast.NodeVisitor):
  663.     def visit_If(self,node): #visit if statement node
  664.         global converted_lines, top_level_if #have access to relevant globals
  665.  
  666.         if_block = [] #make a list of the if block
  667.         condition = general_access_node(node.test) #convert the condition of the if statement analysing the node
  668.         condition_string = '' #make a string of the condition statement
  669.         for i in range(0,len(condition)): #iterate over the arguments of the condition, each should have already been converted to an appropriate format
  670.             condition_string += str(condition[i]) + ' ' #add the condition arguments space separated
  671.         condition_string = condition_string[:-1] #remove the extra space at the end
  672.         if(top_level_if): #if this is the first if statement
  673.             statement = 'if (%s) {' % condition_string #format it as opening if with the condition
  674.             top_level_if = False #next if statement is not top level, i.e. there is an elif statement
  675.         else: #if an elif statement is already present
  676.             statement = 'else if (%s) {' % condition_string #format with else if instead
  677.         if_block.append(statement) #append the statement to the if block
  678.  
  679.         for i in node.body: #iterate over the nodes in the body of the if block
  680.             #there could potentially be more if statements nested inside the main if statement
  681.             #the first nested ifs would need to be if and not elif , hence reset the flag to be top level
  682.             top_level_if = True
  683.             line = general_access_node(i) #convert the node to an appropriate format
  684.             if_block.append(line) #append the line to the if_block
  685.             top_level_if = False #flag top level back to false as nesting has finished
  686.            
  687.         if_block.append('}') #close the if statement block
  688.  
  689.         #check if the else block contains another if block (this occurs if an elif statement is used)
  690.         if(node.orelse == [] or type(node.orelse[0]) != ast.If): #if no elif statement in if block
  691.             if_block.append('else {') #append an else statement
  692.         else:
  693.             pass
  694.        
  695.         #@todo fix where an else if appears for the first if inside an else block
  696.         #example problem:
  697.         #else {
  698.         #   else if (...) {
  699.         #  
  700.         #   }
  701.         #   else {
  702.         #  
  703.         #   }
  704.         #}
  705.         for i in node.orelse: #iterate over the nodes in the else statement
  706.             try: #if its an elif statement i.test will work else will run except
  707.                 i.test
  708.                 line = general_access_node(i) #convert line
  709.                 if_block.append(line) #add line to if block
  710.             except: #if fails deafult to top level if statement for any statement inside
  711.                 top_level_if = True #mark top level
  712.                 line = general_access_node(i) #get the type of line and convert
  713.                 if_block.append(line) #append the line to the if block
  714.                 top_level_if = False #reset top level to false
  715.  
  716.         #@todo figure out a better way to ensure correct number of closing braces
  717.         if(if_block[-1][-1] == '}'): #check if all if statements got closed off
  718.             pass
  719.         else: #if not close off the final one
  720.             if_block.append('}')
  721.  
  722.         top_level_if = True #reset top level back to true
  723.  
  724.         return if_block #return the converted if block
  725.  
  726. #define parser for compare statement nodes, inside if blocks
  727. class CompareParser(ast.NodeVisitor):
  728.     def visit_Compare(self,node): #visit the compare node
  729.         left_arg = general_access_node(node.left) #store the left value of the argument
  730.         #define the types of ast operators
  731.         ast_ops = [ast.Eq,ast.NotEq,ast.Lt,ast.LtE,ast.Gt,ast.GtE,ast.Is,ast.IsNot,ast.In,ast.NotIn,ast.And,ast.Or]
  732.         #define the corresponding c++ operators
  733.         c_ops = ['==','!=','<','<=','>','>=','TODO','TODO','TODO','TODO','&&','||']
  734.        
  735.         #@todo handle the is and in operators for c++
  736.         full_args = [] #list of full arguments from the compare statement
  737.         full_args.append(left_arg) #append the left value argument to full arguments
  738.         for i in range(0,len(node.ops)): #iterate over the number of operators for long chains of comparisons
  739.             index = ast_ops.index(type(node.ops[i])) #get the index of match of the ast_ops
  740.             c_op = c_ops[index] #get the corresponding c_op
  741.             full_args.append(c_op) #append the c operator to the args
  742.             value = general_access_node(node.comparators[i]) #get the value being compared to type and value
  743.             if(type(value) == str): #if it is a string
  744.                 value = string_or_var(value) #run check to see if it is string or variable
  745.             else:
  746.                 pass
  747.             full_args.append(value) #append the value to the full args
  748.        
  749.         return full_args #return the full args
  750.  
  751. #define parser for name nodes
  752. class NameParser(ast.NodeVisitor):
  753.     def visit_Name(self,node): #visit name node
  754.         name = node.id #get the name id
  755.         return name #return the name
  756.  
  757. #define parser for for loop nodes
  758. class ForParser(ast.NodeVisitor):
  759.     def visit_For(self,node): #visit the for node
  760.         global converted_lines
  761.         iterator = general_access_node(node.target) #get the iterator of the loop
  762.         condition = general_access_node(node.iter) #get the condition of the loop
  763.         try: #check for a special condition of iterating over lines in an open file
  764.             file = False #flag for whether this loop is accessing a file
  765.             file_check = 'std::fstream %s;' % condition #check for declaration of file
  766.             if(type(condition) != tuple): #will only be a file declaration if condition is not a tuple
  767.                 for line in converted_lines: #iterate over converted lines to look for file declaration
  768.                     if(type(line) == list):
  769.                         for i in line:
  770.                             if(i==file_check):
  771.                                 file = True #if file declaration found flag as such
  772.                                 break
  773.                             else:
  774.                                 pass
  775.                     else:
  776.                         if(line==file_check):
  777.                             file = True #if file declaration found flag as such
  778.                             break
  779.                         else:
  780.                             pass
  781.             #this condition should be true for an iterative loop of type "for line in file:"
  782.             if(type(condition) != tuple and file==True):                
  783.                 for_condition = [] #make a block for this type of for loop
  784.                 declare_iterator = 'std::string %s;' % iterator #make iterator declaration
  785.                 new_condition = "while (!%s.eof()) {" % condition #conver to a while loop to iterate over file
  786.                 getline_string = "std::getline(%s,%s,'\\n');"% (condition,iterator) #get the line of the file
  787.                 #append relevant statements to condition block
  788.                 for_condition.append(declare_iterator)
  789.                 for_condition.append(new_condition)
  790.                 for_condition.append(getline_string)
  791.                 for i in node.body: #for each node in the body
  792.                     line = general_access_node(i) #classify and convert the line
  793.                     if('std::cout << ' in line): #check if attempting to print something
  794.                         splitup = line.split(' << ') #split on the args separator
  795.                         for i in range(0,len(splitup)): #iterate over the split args
  796.                             if(splitup[i] == ('"%s"' % iterator)): #check if the arg is the iterator which has falsely been converted in to a string
  797.                                 splitup[i] = iterator #if false conversion made switch out for iterator
  798.                                 line = ' << '.join(splitup) #rejoin the line
  799.                             else: #if no false string pass
  800.                                 pass
  801.                     elif('push_back' in line): #check if attempting to append something to a list
  802.                         if(('push_back("%s")' % iterator) in line): #if iterator being pushed back after false conversion to a string replace the string with the iterator
  803.                             line = line.replace(('push_back("%s")' % iterator), ('push_back(%s)' % iterator))
  804.                         else:
  805.                             pass
  806.                     for_condition.append(line) #convert the node and append it to the block
  807.                 for_condition.append('}') #close the for brace
  808.                 return for_condition #return this special case loop
  809.             else:
  810.                 pass
  811.         except: #if fails condition is not true anyway
  812.             pass
  813.         #e.g. of condition = ('range'[0,list_name.size()]) or number equivalent
  814.         if(condition[0] == 'range'):
  815.             lower_limit = condition[1][0] #lower limit of range
  816.             upper_limit = condition[1][1] #upper limit of range
  817.             #write the condition of the for loop incrementing in the range
  818.             if(upper_limit==0): #if the upper limit is 0, e.g. range(10,0)
  819.                 #make the for condition iterate backwards from the "lower" limit (with higher value) to "upper" limit (value 0)
  820.                 for_condition = 'for (int %s = %s; %s > %s; %s--) {' % (iterator,lower_limit,iterator,upper_limit,iterator)
  821.             elif(isinstance(upper_limit,int) and isinstance(lower_limit,int) and upper_limit<lower_limit): #if both limits are numbers (not a len function) and upper has lower value than lower
  822.                 #make the for condition iterate backwards from the "lower" limit (with higher value) to "upper" limit (lower value)
  823.                 for_condition = 'for (int %s = %s; %s > %s; %s--) {' % (iterator,lower_limit,iterator,upper_limit,iterator)
  824.             else: #otherwise assume forwards iteration
  825.                 for_condition = 'for (int %s = %s; %s < %s; %s++) {' % (iterator,lower_limit,iterator,upper_limit,iterator)
  826.         elif(condition[0] == 'reversed'):
  827.             #condition will be in format ('reversed',[('range',[0,5])])
  828.             #take limits opposite way round as they are in a reversed function
  829.             upper_limit = condition[1][0][1][0]
  830.             lower_limit = condition[1][0][1][1]
  831.             if(upper_limit==0): #conditions are as above but reversed
  832.                 for_condition = 'for (int %s = %s; %s > %s; %s--) {' % (iterator,lower_limit,iterator,upper_limit,iterator)
  833.             elif(isinstance(upper_limit,int) and isinstance(lower_limit,int) and lower_limit>upper_limit):
  834.                 for_condition = 'for (int %s = %s; %s > %s; %s--) {' % (iterator,lower_limit,iterator,upper_limit,iterator)
  835.             else:
  836.                 for_condition = 'for (int %s = %s; %s < %s; %s++) {' % (iterator,lower_limit,iterator,upper_limit,iterator)
  837.         else: #if line was for x in list_name, the condition will be (list_name)
  838.             vector = condition
  839.             #format for condition
  840.             for_condition = 'for (auto %s: %s) {' % (iterator,vector)
  841.  
  842.         body_block = [] #define body of for loop
  843.         body_block.append(for_condition) #append the for condition to the body
  844.         for i in node.body: #for each node in the body
  845.             line = general_access_node(i) #classify and convert the line
  846.             if('std::cout << ' in line): #check if attempting to print something
  847.                 splitup = line.split(' << ') #split on the args separator
  848.                 for i in range(0,len(splitup)): #iterate over the split args
  849.                     if(splitup[i] == ('"%s"' % iterator)): #check if the arg is the iterator which has falsely been converted in to a string
  850.                         splitup[i] = iterator #if false conversion made switch out for iterator
  851.                         line = ' << '.join(splitup) #rejoin the line
  852.                     else: #if no false string pass
  853.                         pass
  854.             elif('push_back' in line):
  855.                 if(('push_back("%s")' % iterator) in line):
  856.                     line = line.replace(('push_back("%s")' % iterator), ('push_back(%s)' % iterator))
  857.                 else:
  858.                     pass
  859.             body_block.append(line) #convert the node and append it to the block
  860.         body_block.append('}') #close the for brace
  861.         return body_block #return the body
  862.  
  863. #define parser for attribute nodes
  864. class AttributeParser(ast.NodeVisitor):
  865.     def visit_Attribute(self,node): #visit the attribute node
  866.         attribute = node.attr #gives the function being applied, e.g for a.append, returns append
  867.         value = general_access_node(node.value) #classify and convert the object being appended
  868.         #print(attribute,value)
  869.         if(attribute=='append'): #if the atrribute is append
  870.             attribute = 'push_back' #replace with vector push_back method
  871.         elif(value=='self'): #if not raise a type error to flag this attribute is not yet handled
  872.             #converted_line = '%s.%s' % (value,attribute)
  873.             return attribute
  874.             #return None
  875.             #raise TypeError('Attribute type not handled yet: %s,%s' % (attribute,value))
  876.         else:
  877.             pass
  878.        
  879.         converted_line = '%s.%s(' % (value,attribute) #define the converted line
  880.         return converted_line #return the converted line
  881.  
  882. #define parser for while nodes
  883. class WhileParser(ast.NodeVisitor):
  884.     def visit_While(self,node): #visit while node
  885.         while_body = [] #define while block
  886.         condition = general_access_node(node.test) #convert the while condition
  887.         condition_string = '' #define string for the condition
  888.         for i in condition: #iterate over the elements in the condition list
  889.             condition_string+=str(i)+' ' #add the condition element and a space
  890.         condition_string = condition_string[:-1] #remove the extra space
  891.         condition_line = 'while (%s) {' % condition_string #define the while statement
  892.         while_body.append(condition_line) #append the while statement to the block
  893.         for i in node.body: #iterate over nodes in the body of the while loop
  894.             line = general_access_node(i) #classify and convert the node
  895.             while_body.append(line) #append the converted line to the block
  896.         while_body.append('}') #close the while loop
  897.         #@todo handle the orelse node
  898.         return while_body #return the while block
  899.  
  900. #define parser for AugAssign nodes
  901. class AugAssignParser(ast.NodeVisitor):
  902.     def visit_AugAssign(self,node): #visit AugAssign nodes
  903.         var = general_access_node(node.target) #get the variable value is assigned to
  904.         ast_operators = [ast.Add,ast.Sub,ast.Div,ast.Mult] #define list of ast operators
  905.         c_ops = ['+=','-=','/=','*='] #define corresponding list of c++ operators
  906.         index = ast_operators.index(type(node.op)) #get the index matching the operator
  907.         operator = c_ops[index] #get the correspoding c++ operator to the matched operator
  908.         value = general_access_node(node.value) #classify and convert the value node
  909.         converted_line = '%s %s %s;' % (var,operator,value) #format the aug assign string
  910.         return converted_line #return the converted line
  911.  
  912. #define parser for name constant nodes
  913. class NameConstantParser(ast.NodeVisitor):
  914.     def visit_NameConstant(self,node): #visit name constant node
  915.         #node.value should be True False or None
  916.         if(node.value == True): #if True return C++ bool true
  917.             return 'true'
  918.         elif(node.value == False): #if false return C++ bool false
  919.             return 'false'
  920.         else: #if none flag not handled yet
  921.             raise TypeError('NameConstant not true or false and not handled : %s' % node.value)
  922.  
  923. #define function to check if a value is a string or a variable as they are classified the same by the AST
  924. def string_or_var(value):
  925.     global converted_lines, arg_vars, function_body, class_args #have access to relevant globals
  926.     found = False #flag no match found
  927.     #iterate backwards over converted lines looking for a match, backwards to get most recent definition
  928.     for i in reversed(range(0,len(converted_lines))):
  929.         declaration_check = '%s = ' % value #look for declaration of variable
  930.         if(declaration_check not in converted_lines[i]): #if not match pass
  931.             pass
  932.         else: #if match flag a match found
  933.             found = True
  934.             break
  935.     #if there is a function under conversion not yet appended and no match found
  936.     if(arg_vars != [] and found == False):
  937.         for i in range(0,len(arg_vars)): #iterate over the function arguments
  938.             if(value not in arg_vars[i]): #if no match pass
  939.                 pass
  940.             else: #if match flag a match was found
  941.                 found = True
  942.                 break
  943.     else:
  944.         pass
  945.     #if there is a function under conversion not yet appended and no match found
  946.     if(function_body != [] and found == False):
  947.         for i in reversed(range(0,len(function_body))): #iterate backwards over function body to get most recent definition
  948.             declaration_check = '%s = ' % value #look for declaration of variable
  949.             if((converted_lines==[]) or declaration_check not in converted_lines[i]): #if no match pass
  950.                 pass
  951.             else: #if match flag that a match was found
  952.                 found = True
  953.                 break
  954.            
  955.     #if there is an active class check for a match in its declarated arguments
  956.     if(class_args != [] and found == False):
  957.         for i in range(0,len(class_args)): #iterate over class args to check for match
  958.             declaration_check = '%s' % value #look for declaration of variable
  959.             if((class_args==[]) or declaration_check not in class_args[i]): #if no match pass
  960.                 pass
  961.             else: #if match flag that a match was found
  962.                 found = True
  963.                 break
  964.    
  965.    
  966.     if(found==False):
  967.         #print(converted_lines)
  968.         second_declare = ' %s;' % value
  969.         for i in reversed(range(0,len(converted_lines))):
  970.             for j in range(0,len(converted_lines[i])):
  971.                 if(second_declare not in converted_lines[i][j]): #if not match pass
  972.                     pass
  973.                 else: #if match flag a match found
  974.                     found = True
  975.                     break
  976.    
  977.     if(found == False and value != 'true' and value != 'false'): #if no match default to string
  978.         value = '"%s"' % value
  979.     else:
  980.         pass
  981.     #if match will return the same value therefore a variable, if no match it will put speech marks around for c++ string definition
  982.     return value
  983.  
  984. #this functions purpose is to receive any node and determine it's type
  985. #once determined the node will be passed to an appropriate parsing function or directly
  986. #handle the node for some simple cases then return the value of the node after it has
  987. #been parsed and converted by the parser it sent it to
  988. def general_access_node(node):
  989.     #check the type of the node and compare to currently handled type
  990.     if(type(node) == ast.FunctionDef):
  991.         #store the value of the return from the parsed node after sending it to be decoded
  992.         parsed_node = FunctionParser().visit_FunctionDef(node)
  993.     elif(type(node) == ast.Assign):
  994.         parsed_node = AssignParser().visit_Assign(node)
  995.     elif(type(node) == ast.Expr):
  996.         parsed_node = ExprParser().visit_Expr(node)
  997.     elif(type(node) == ast.If):
  998.         parsed_node = IfParser().visit_If(node)
  999.     elif(type(node) == ast.For):
  1000.         parsed_node = ForParser().visit_For(node)
  1001.     elif(type(node) == ast.Num):
  1002.         parsed_node = NumParser().visit_Num(node)
  1003.     elif(type(node) == ast.Str):
  1004.         parsed_node = StrParser().visit_Str(node)
  1005.     elif(type(node) == ast.UnaryOp):
  1006.         parsed_node = UnaryOpParser().visit_UnaryOp(node)
  1007.     elif(type(node) == ast.Subscript):
  1008.         parsed_node = SubscriptParser().visit_Subscript(node)
  1009.     elif(type(node) == ast.Call):
  1010.         parsed_node = CallParser2().visit_Call(node)
  1011.     elif(type(node) == ast.Return):
  1012.         parsed_node = ReturnParser().visit_Return(node)
  1013.     elif(type(node) == ast.Tuple):
  1014.         parsed_node = TupleParser().visit_Tuple(node)
  1015.     elif(type(node) == ast.List):
  1016.         parsed_node = ListParser().visit_List(node)
  1017.     elif(type(node) == ast.Compare):
  1018.         parsed_node = CompareParser().visit_Compare(node)
  1019.     elif(type(node) == ast.Name):
  1020.         parsed_node = NameParser().visit_Name(node)
  1021.     elif(type(node) == ast.Pass):
  1022.         parsed_node = '\n'
  1023.     elif(type(node) == ast.Attribute):
  1024.         parsed_node = AttributeParser().visit_Attribute(node)
  1025.     elif(type(node) == str):
  1026.         parsed_node = node
  1027.     elif(type(node) == ast.While):
  1028.         parsed_node = WhileParser().visit_While(node)
  1029.     elif(type(node) == ast.AugAssign):
  1030.         parsed_node = AugAssignParser().visit_AugAssign(node)
  1031.     elif(type(node) == ast.NameConstant):
  1032.         parsed_node = NameConstantParser().visit_NameConstant(node)
  1033.     elif(type(node) == ast.ClassDef):
  1034.         parsed_node = ClassDefParser().visit_ClassDef(node)
  1035.     elif(type(node) == ast.Break):
  1036.         parsed_node = 'break;'
  1037.     else: #if the type of node does not yet have a parser raise a type error which diesplays the type to know what parser needs to be made next
  1038.         raise TypeError('Parser not found for type: %s' % type(node))
  1039.    
  1040.     return parsed_node #return the parsed/converted node value
  1041.  
  1042. #define the main function for parsing and converting a script,takes arguments of the name of a python script and the name of a script with example function calls
  1043. def main(script_to_parse,script_of_function_calls=None):
  1044.     global converted_lines, list_types #make globals of the converted lines and function argument types
  1045.     list_types = [] #define list of function types from script of function calls
  1046.     converted_lines = [] #define list of converted C++ lines
  1047.     #if a script of cuntion calls has been provided analyse it for types, if not then no functions are defined in the python script
  1048.     if(script_of_function_calls!=None):
  1049.         file2 = open(script_of_function_calls,'r').read() #open and read the script of function calls specified
  1050.         call_parse = ast.parse(file2) #parse the script to make an AST of nodes
  1051.         for node in call_parse.body: #iterate over the nodes in the body of the tree
  1052.             funcs_args = [] #define list of the arguments of current function in the tree
  1053.             for arg in node.value.args: #iterate over the arguments in the function currently active
  1054.                 arg_val = general_access_node(arg) #get the value of the argument
  1055.                 #run through a series of checks to determine the type of the argument provided
  1056.                 #once a match is found append the appropriate type to the current function's arguments type list
  1057.                 if isinstance(arg_val,int):
  1058.                     funcs_args.append('int')
  1059.                 elif isinstance(arg_val,float):
  1060.                     funcs_args.append('float')
  1061.                 elif isinstance(arg_val,str):
  1062.                     funcs_args.append('std::string')
  1063.                 elif isinstance(arg_val,list): #if the argument is a list it needs special handling
  1064.                     inside_level = arg_val[0] #get the first element of the list to check if it contains more lists
  1065.                     nest_level = 1 #increase the nest level by one
  1066.                     if isinstance(inside_level,list): #if the first element is a list
  1067.                         inside_level = inside_level[0] #take the first element of the sub list
  1068.                         nest_level+=1 #increase the nest level by one
  1069.                         while(isinstance(inside_level,list)): #if still a sub list then repeat this process until the first non list element is found
  1070.                             inside_level = inside_level[0]
  1071.                             nest_level+=1
  1072.                         type_check = type(inside_level) #get the type of the first non list element
  1073.                        
  1074.                     else: #if not a list of lists get the type of the first element
  1075.                         type_check = type(inside_level)
  1076.                     #define the type of list, nest level is used to determine the number of vector commands to nest
  1077.                     #for example the list [[2,3,4],[5,6,7]] would have nest_level = 2 and type_check = <class 'int'>
  1078.                     #so the below code would format type_list = <std::vector<std::vector<int>>
  1079.                     type_list = ('std::vector<'*nest_level)+str(type_check).replace("<class '",'').replace("'>",'') + ('>'*nest_level)
  1080.                     funcs_args.append(type_list) #append this vector definition to the function's arguments
  1081.             list_types.append(funcs_args) #append the completed funtion arguments type list to the list of type lists
  1082.     else: #if no function calls skip this process
  1083.         pass
  1084.    
  1085.     file = open(script_to_parse,'r').read() #open the python file to convert and read it
  1086.     tree = ast.parse(file) #make an AST of the nodes within the file
  1087.     main = False #flag that a main function has not yet been added to the converted script
  1088.     for node in tree.body: #iterate over the nodes in the body of the AST
  1089.         line_test = general_access_node(node) #run function to determine the type of the node and convert it accordingly
  1090.         #if the line had a function definition it has already been added to converted lines so this condition is added to stop duplicate addition, these are the conditions that will be met if that is true
  1091.         #print(line_test[0])
  1092.         if(('auto' in line_test[0] and '{' in line_test[0]) or ('class' in line_test[0] and '{' in line_test[0])):
  1093.             pass
  1094.         else: #if it is not a function definition
  1095.             if(main==False): #check if a main has been added yet
  1096.                 converted_lines.append('int main () {') #if no main start the main function here as function definitions have finished
  1097.                 main=True #flag that a main has been added
  1098.             else: #if main has been added do not add it again
  1099.                 pass
  1100.         #check to see if the returned value has already been added to the converted lines and the return was not a void one
  1101.         #if it is not a NoneType return and has not yet been added to converted lines then add it
  1102.         #modified to check if the line was just appended to the converted lines list, may cause issue if you write the same line twice in a row, untested
  1103.         #if(line_test != converted_lines[len(converted_lines)-1] and line_test != None):
  1104.         converted_lines.append(line_test)
  1105.         #else: #if it has been addded or is NoneType do nothing
  1106.          #   pass
  1107.     converted_lines.append('return 0;') #add a return for the c++ main function
  1108.     converted_lines.append('}') #close the main function
  1109.     #below are checks to see what include statements are needed for the c++ code
  1110.     #each one will chec for an instance of a function and if a match is found the appropriate
  1111.     #include statement will be inserted in to the top of the converted lines list
  1112.     if(('std::cout' in line for line in converted_lines) or ('std::cin' in line for line in converted_lines)):
  1113.         converted_lines.insert(0,'#include <iostream>')
  1114.     else:
  1115.         pass
  1116.     if('std::string' in line for line in converted_lines):
  1117.         converted_lines.insert(0,'#include <string>')
  1118.     else:
  1119.         pass
  1120.     if('std::vector' in line for line in converted_lines):
  1121.         converted_lines.insert(0,'#include <vector>')
  1122.     else:
  1123.         pass
  1124.     if('std::fstream' in line for line in converted_lines):
  1125.         converted_lines.insert(0,'#include <fstream>')
  1126.     else:
  1127.         pass
  1128.     return converted_lines #return the list of converted c++ lines
  1129.  
  1130. #function to check if line is list or list of lists and convert in to flattened data
  1131. def walk(e):
  1132.     if(type(e) == list): #if the line is a list
  1133.         for v2 in e: #iterate over list elements
  1134.             for v3 in walk(v2): #iterate over sub list elements (which will iterate further if another sublist)
  1135.                 yield v3 #yield the non list element
  1136.     else: #if line not a list
  1137.         yield e #return the line
  1138.  
  1139. #function to write the parsed data to an output .cpp file
  1140. def write_file(data,name_of_output='Output.cpp'):
  1141.     file = open(name_of_output,'w+') #open an output file to the specified path for writing/creation
  1142.     indentation_level=0 #default indentation level is 0
  1143.     public_open = False
  1144.     flatten = [] #create a list of flattened line data
  1145.     for line in walk(data): #call walk function on converted data
  1146.         flatten.append(line) #append the flattened line to the flattened data
  1147.  
  1148.     for line in flatten: #iterate over the lines in the flattened data
  1149.         #print(line,indentation_level)
  1150.         if(public_open==True and ('auto' in line or '};' in line)):
  1151.             indentation_level-=1
  1152.             public_open=False
  1153.         else:
  1154.             pass
  1155.         open_brace_count = line.count('{') #count number of open brackets on the line
  1156.         close_brace_count = line.count('}') #count number of closing brackets on the line
  1157.         if(open_brace_count>close_brace_count):
  1158.             #if more open brackets than close, the code following (not including) this line
  1159.             #will require indentation, as such write this line and the increase the indentation level for subsequent lines
  1160.             file.write(('\t'*indentation_level)+line+'\n')
  1161.             indentation_level+=1
  1162.         elif(open_brace_count<close_brace_count):
  1163.             #if more close brackets than open brackets, the code following is outside of the
  1164.             #function block, as such will need indentation level reduced by one, this includes the closing brace this time
  1165.             #hence the reverse order to the condition above
  1166.             indentation_level-=1
  1167.             file.write(('\t'*indentation_level)+line+'\n')
  1168.         elif('public:' in line):
  1169.             file.write(('\t'*indentation_level)+line+'\n')
  1170.             indentation_level+=1
  1171.             public_open = True
  1172.         else: #if number of braces equal then do not change indentation level
  1173.             file.write(('\t'*indentation_level)+line+'\n')
  1174.  
  1175.     file.close() #writing completed so close the file
  1176.  
  1177. if __name__ == '__main__':
  1178.     top_level_if = True #flag for if statments, method needs revision
  1179.     list_spiel = True #flag for displaying extra information about empty lists
  1180.     class_vars_for_call = []
  1181.     called_objs = []
  1182.     print('Beginning Parsing') #inform user parsing has began, precaution incase a large file takes a long time parsing
  1183.     converted_data = main('Test.py','CallTest.py') #parse Test.py file, function call examples are listed in CallTest.py, if no function calls do not pass second argument
  1184.     print('Parsing Completed') #inform user parsing has finished
  1185.     print('Writing File') #inform user output to file has started
  1186.     write_file(converted_data,'Test.cpp') #write a file called Test.cpp with the output data, if no name specified will default to Output.cpp
  1187.     print('Writing Completed') #inform user writeout completed
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement