NLinker icon

C++ function definition parser (Python interop)

NLinker | PRO | 01/28/21 10:59:10 AM UTC | 0 ⭐ | 1687 👁️ | Never ⏰ | []
Python |

76.27 KB

|

None

|

0 👍

/

0 👎

# py_to_cpp.py
  
#script to read python code in to an AST, parse and replace nodes with corresponding
#c++ code
 
#make relevant imports
import ast
import numpy as np
 
#parser class for function definitions
class FunctionParser(ast.NodeVisitor):
    def visit_FunctionDef(self, node): #visit the function definition node
        #define relevant globals that require access
        global converted_lines, function_body, arg_vars, list_types, class_args
        arg_vars = [] #list of arguments
        args_string = '' #argument string for conversion
        init_arg = [] #store a list of arguments to initialise, done for use in any class definitions
        
        for i in range(0,len(node.args.args)): #iterate over the node arguments
            arg_val = node.args.args[i].arg #for each arg get the arg name
            init_arg.append(arg_val) #append the arg name to the list of args to initialise
        
        #class arguments usually start with self, self is not required for c++
        #a type won't have been defined in the function call for self, therefore if different number of list types to arguments
        #and first argument is self, remove the argument so the list of types and arguments matches up again
        if((len(init_arg) != len(list_types[0])) and init_arg[0] == 'self'): 
            init_arg.pop(0)
        else:
            pass
        
        #iterate over the arguments to initialise
        for i in range(0,len(init_arg)):
            arg_type = list_types[0][i] #get the types of the first function's arguments
            full_arg = arg_type + ' ' + init_arg[i] #define a full argument string as the type and name
            arg_vars.append(full_arg) #add the full arg definition to list
            args_string += full_arg + ', ' #add the full arag definition to the arg string
        args_string = args_string[:-2] #remove extra ', ' at the end of the line
        list_types.pop(0) #remove the arg types for the arguments that have just been processed
        
        #if the name of the function is a class initialilser run a special case
        if(node.name == '__init__'):
            class_initialiser = [] #block for class initialisation function
            class_initialiser.append('public:') #mark following class variables as public for initalising the object
            class_args = init_arg #set the class args as a copy of the initialiser args
            for i in node.body: #iterate over the body of the initialiser function
                line = general_access_node(i) #classify and convert the line of the function
                splitup = line.split(' ') #split the converted line by space to inspect elements
                #the following is a messy way to initialise class variables, if it is an initialisation the line will be in the
                #style std::string name = "name"; which is incorrect formatting due to how these statements are processed elsewhere
                try:
                    #check if the first argument of splitup (name) is equal to the final element of splitup inside quotations
                    #and without the ; ("name";).
                    if(splitup[1] == ("%s" % splitup[3][:-1]).replace('"','')):
                        #if it is then its a variable declaration, take the converted arg_vars declaration and add a semicolon
                        #it will now be in the form std::string name; (or other appropriate type of variable)
                        string_val = arg_vars[0]+';' 
                        class_initialiser.append(string_val) #append the new string to the block
                        arg_vars.pop(0) #remove the arg_var as it has been declared in block
                    else: #if it doesn't match just append the line
                        class_initialiser.append(line)
                except: #if splitup element access fails just append the line as the above condition will not occur
                    class_initialiser.append(line)
            return class_initialiser #return the initialised class function
        else:
            pass
        
        function_body = [] #define list for the main body of the function
        for i in node.body: #iterte over the nodes in the body of the function
            if(type(i) == ast.Return): #check if the line is a return function
                line = ReturnParser().visit_Return(i) #visit the return parser function
                if(line == None): #if return is a void return
                    function_body.append('return') #add a void return to the body
                else: #if return has arguments
                    return_types = [] #make a list of the types of values being returned
                    for j in range(0,len(line)):  #iterate over the return values listed
                        for i in reversed(range(0,len(function_body))): #iterate backwards over the body of the function (find the latest definitions of the variables)
                            declaration_check = ' %s = ' % line[j] #check for a definition of the variable
                            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
                                pass
                            else: #if a definition is found isolate they type by taking the first word and add it to the return types list
                                return_types.append(function_body[i].split(' ')[0])
                    #if there is only one return value a normal return can be used 
                    if(len(return_types) == 1):
                        #add the return and the value to the function body
                        function_body.append('return %s;' % line)
                    else: #if multiple values are required generate a structure to return
                        struct_string = 'struct result {' #initialise structure string
                        for i in range(0,len(line)): #iterate over the number of arguments
                            #add (value_type dummy_return_[number];) to the structure string  
                            struct_string += return_types[i] + ' dummy_return_%s; ' % str(i)
                        #remove the extra space, close the struct bracket and end statement for struct definition
                        struct_string = struct_string[:-1] + '};'
                        function_body.append(struct_string) #add the struct definition to the function body
                        return_string = 'return result {' #create string for function returns using the structure just defined
                        for i in range(0,len(line)): #iterate over the return arguments
                            return_string += line[i] + ', ' #add the arguments to the return string
                        return_string = return_string[:-2] + '};' #remove the extra ', ' and close bracket and end return statement
                        function_body.append(return_string) #add the return string to the function body
            else: #if the node is not a return determine the type and convert it then add to function body
                function_body.append(general_access_node(i))
        if('return' in function_body[-1]): #check if the function was ended with a return
            pass
        else: #if no return add one
            function_body.append('return;')
        if(node.name == 'main'): #add a catch to prevent duplicate main functions within the converted script
            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')
        else:
            pass
        
        function = [] #block for whole function
        #define the c++ function definition with the function name and the arguments string
        function_def = 'auto %s (%s) {' % (node.name, args_string)
        function.append(function_def) #add the function definition line
        function.append(function_body) #add the function body
        function.append('}') #close the open function brace
        arg_vars = [] #reset arg_vars as it is global
        function_body = [] #reset function body as it is global
        return function #return whole function
 
#define parser for class definitions
class ClassDefParser(ast.NodeVisitor):
    def visit_ClassDef(self,node): #visit class definition node
        global class_args #access to relevant globals
        class_block = [] #block of lines of converted class
        class_name = node.name #get the name of the class
        class_dec = 'class %s {' % class_name #make a converted class statement
        class_block.append(class_dec) #append the class stement to the block
        
        class_body = [] #make list of body of class statement
        for i in node.body: #iterate over the nodes in the body
            line = general_access_node(i) #convert the line of nodes and return
            class_body.append(line) #append the converted line to the body
        
        class_block.append(class_body) #append the body to the block
        class_block.append('};') #close off the class definition
        
        #@todo these properties
        #print(node.bases)
        #print(node.keywords)
        #print(node.decorator_list)
        return class_block #return the class block
 
#input classifying and converting function
def input_convert(name,data,type_var): #pass args of the variable, input arg data and the type of variable (pre-determined)
    val_assign = data #set val assign as data
    name_assign = name #set name assign as name
    converted_input = [] #list of converted input lines
    args = val_assign[1] #store the args of the input string
    outstring = args[0] #argument of the input string (line to output to prompt an input)
    outstring = string_or_var(outstring) #check if the outstring is a variable or string question
    outline = 'std::cout << ' + outstring + ';' #output the outstring
    converted_input.append(outline) #store the output line
    var = name_assign #store name of variable under var
    set_var = type_var + ' ' + var +';' #declare the input variable using the previously found type
    converted_input.append(set_var) #append the declaration to the input lines
    if(type_var=='std::string'): #if the type of input is a string
        input_string = 'std::getline (std::cin, %s);' % var #format a getline command to avoid whitespace breaks as this is probably unintended
        converted_input.append(input_string) #append the getline command to the input lines
    else: #if some other var type like float or int
        input_string = 'std::cin >> %s;' % var #use a standard cin command
        converted_input.append(input_string) #append the input command
        clear_in_buffer = 'std::cin.get();' #add a line to clear the input buffer to remove trailing \n for future input
        converted_input.append(clear_in_buffer) #append the clear buffer command
    end_line = 'std::cout << std::endl;' #append command to end line after inputs so next line isn't printed on the same line
    converted_input.append(end_line) #append the end line command to the input conversion
    return converted_input #return the input conversion
 
#parser class for assign statements, anything with an =
class AssignParser(ast.NodeVisitor):
    def visit_Assign(self,node): #function to visit the assign node
        global converted_lines, arg_vars, class_vars_for_call, called_objs, list_spiel #access to required globals
        for name in node.targets: #iterate over the node targets
            name_assign = general_access_node(name) #get the name, this is the variable the value is assigned to
        if(type(node.value) == ast.BinOp): #check for binary operators in the statement
            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
            flatten = [] #list for converting any sublists to one 1D array
            for i in val_assign: #iterate over the values
                if isinstance(i,list): #if one of the values is a list
                    for j in i: #iterate the values in that list and append to the flattened array
                        flatten.append(j)
                else: #if not a list just append the values directly
                    flatten.append(i)
            if(flatten!=[]): #if anything was added to the flatten list set the list as the value for the assign
                val_assign = flatten
            else:
                pass
            val_assign = ['BinOp',val_assign] #specify that this was a BinOp assignment 
        else: #if it wasn't a bin op find the type of node and convert the value to appropriate formatting
            val_assign = general_access_node(node.value)
        
        try:
            if(name_assign==("%s" % val_assign)): #if name_assign assign and val assign are the same
                class_vars_for_call.append(val_assign) #store the val in a list of class variables
            else:
                pass
        except:
            pass
 
        type_check = type(val_assign) #find the type data the value assign is
        if(type_check == tuple and val_assign[0] == 'open'): #this condition will be met if the user attempts to open a file
            #val_assign will be in format (open,[filename,r/w/a])
            file_conversion = [] #make a list for file operation conversion
            args = val_assign[1] #args of the
            file_name = args[0] #get the file name as first arg
            open_type = args[1] #get the type of file operation as second arg
            file_declare = 'std::fstream %s;' % name_assign #make declaration of file stream
            file_conversion.append(file_declare) #appedn declaration to file conversion
            #convert appropriate operation type to c++ equivalent
            if('r' in open_type):
                open_type = 'std::ios::in'
            elif('w' in open_type):
                open_type = 'std::ios::out'
            elif(open_type == 'a'):
                open_type = 'std::ios::app'  
                
            file_name = file_name.replace('\\',"/") #replace backslash with forward to prevent issues in file name
            open_line = '%s.open("%s",%s);' % (name_assign,file_name,open_type) #declare line to open file with appropriate type
            file_conversion.append(open_line) #append file opening statement to file conversion block
            return file_conversion #return file conversion block
        #this condition will be met if someone declares an empty list
        elif(val_assign == []):
            if(list_spiel == True): #print this spiel of information if it is the first time this warning has come up
                print('\nAn empty list was detected in your python script, this is a problem for the conversion.')
                print('The C++ equivalent of python lists need to have the data type being entered declared in advance.')
                list_spiel = False
            else:
                pass
            
            appropriate_answer = False #inform user how to classify and enter their list types
            print('\nEmpty list detected called "%s", please enter a data type for the list. Accepted data types are: integer, float, string' % name_assign)
            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.')
            while(appropriate_answer==False): #keep getting an input until an appropriate one is entered
                data_type = input('Please enter a type listed above or "help" for more information: ')
                
                if(data_type.lower() == 'help'): #if user asking for help print examples of each type
                    print('For an integer list type, the list could look like: [1,2,3,4,5]')
                    print('For a float list type, the list could look like [1.1,2.2,3.3,4.4,5.5]')
                    print('For a string list type, the list could look like ["Apples","Oranges","Bananas","Pears"]')
                    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)).')
                    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')
                elif(data_type.lower() == 'integer'): #format list of integers
                    type_check = 'std::vector<int>'
                    val_assign = '{}'
                    appropriate_answer = True
                elif(data_type.lower() == 'float'): #format list of floats
                    type_check = 'std::vector<float>'
                    val_assign = '{}'
                    appropriate_answer = True
                elif(data_type.lower() == 'string'): #format list of strings
                    type_check = 'std::vector<std::string>'
                    val_assign = '{}'
                    appropriate_answer = True
                elif('list' in data_type.lower()): #format list of lists
                    converted_string_lists = data_type.replace('list','std::vector').replace('(','<').replace(')','>')
                    converted_string_lists = converted_string_lists.replace('integer','int').replace('string','std::string')
                    type_check = 'std::vector<%s>' % converted_string_lists
                    val_assign = str(val_assign).replace('[','{').replace(']','}')
                    if('int' in converted_string_lists or 'float' in converted_string_lists or 'std::string' in converted_string_lists):
                        appropriate_answer = True
                    else:
                        print('Invalid input, please try again')
                else:
                    print('Invalid input, please try again')
        #this condition will be met if line is assigning an input to a variable
        elif(type_check == tuple and val_assign[0] == 'input'):
            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())
            return converted #return the conversion
        #this condition met if input wrapped in a type command of int or float or just a standard int/float command
        elif(type_check == tuple and (val_assign[0] == 'int' or val_assign[0] == 'float')):
            if(val_assign[1][0][0] == 'input'): #if an input command wrapped by the function
                converted = input_convert(name_assign,val_assign[1][0],val_assign[0]) #convert input command using var type of the wrapping function
                return converted #return the converted input
            else: #if it's not an input raise a TypeError as it is not handled yet
                #@todo normal int() float() list() str() commands
                raise TypeError('Conversion of arg type not handled %s' % val_assign)
        #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
        elif(type_check == tuple and any(('class %s {' % val_assign[0]) in x for x in converted_lines)):
            #print(val_assign[0], name_assign, val_assign[1])
            #val_assign will have format (Class_Name,[init_arg,init_arg,...])
            obj_declaration = [] #make list for object declaration
            secondary_class_store = [] #secondary list of class
            assign_obj = '%s %s;' % (val_assign[0],name_assign) #definition of creating object conversion
            obj_declaration.append(assign_obj) #add object declaration to body
            args_obj = val_assign[1] #isolate arguments of the object declaration
            count = 0 #iterator for removing used object args
            recall = False #flag for if this is the second time an object of the same type is being created
            for i in range(0,len(called_objs)): #iterate over list of previously called classes
                if(val_assign[0] == called_objs[i][0]): #check if this object is same type as existing
                    recall = True #mark that this is a recall
                    break #stop iterating
                else:
                    pass
            if(recall==False): #if this is the first time this class has been called
                secondary_class_store.append(val_assign[0]) #append the type to secondary list tracking classes called
                for i in range(0,len(args_obj)): #iterate over the object args
                    if(type(args_obj[i]) == str): #if type of arg is a string
                        args_obj[i] = string_or_var(args_obj[i]) #check if was a string or variable, replace it with variable if variable
                    else:
                        pass
                    secondary_class_store.append(class_vars_for_call[i]) #store the class variable name
                    converted_line = '%s.%s = %s;' % (name_assign,class_vars_for_call[i],args_obj[i]) #initialise class parameters with values
                    count+=1 #increase count of variables from class_vars_for_call used
                    obj_declaration.append(converted_line) #append the converted line to the declaration block of the object
                called_objs.append(secondary_class_store) #append previously called objects with the secondary list of class info
                class_vars_for_call = class_vars_for_call[count:] #omit the used up variables from the class_vars_for_call list
            else: #if the class has been called before
                for j in range(0,len(called_objs)): #iterate over the previously called objects list
                    if(called_objs[j][0] == val_assign[0]): #find the match case for this call
                        for k in range(1,len(called_objs[j])): #iterate over the arguments in that match call
                            #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
                            #therefore called_objs[j] = ['Class_name',arg1,arg2,...], so for each k the corresponding arg to use is k-1
                            if(type(args_obj[k-1]) == str): #if type of arg is a string
                                args_obj[k-1] = string_or_var(args_obj[k-1]) #check if was a string or variable, replace it with variable if variable
                            else:
                                pass
                            converted_line = '%s.%s = %s;' % (name_assign,called_objs[j][k],args_obj[k-1]) #initialise class parameters with values
                            obj_declaration.append(converted_line) #append the converted line to the object declaration
                        break #break loop as relevant match found
                    else:
                        pass
            return obj_declaration #return the object declaration
        #if the val_assign is a return from the subscript parser this condition will be met
        elif(type_check == tuple and val_assign[0] == 'subscript'):
            #val_assign here will be ('subscript',['index',list_name,index_value])
            args = val_assign[1] #arguments of the assign statement stored
            list_name = args[1] #the name of the list is the argument 1
            type_script = args[0] #the type of subscripting (index/slice) stored
            if(type_script == 'index'): #if it is index subscripting
                subscript = list_name + '[%s]' % args[2] #the subscript formtating is list_name[index_value]
            elif(type_script == 'slice'): #if it is slice subscripting
                subscript = list_name + '[%s:%s]' % (args[2],args[3]) #the subscript formatting is list_name[lower_index:upper_index]
            else: #raise type error as other types are not handled yet
                raise TypeError('Subscript type not yet handled: %s' % type_script)
            
            found = False #flag for if a declaration of list has been found
            #multiple iterating methods will follow this to check different places for declaration of the list
            #check the converted lines so far in reverse order to get the most recent declaration of the list
            for i in reversed(range(0,len(converted_lines))):
                find_def = '%s = ' %  list_name #check for declaration of the list 
                if(find_def in converted_lines[i]): #check if the definition is on the current converted line
                    type_check = converted_lines[i].split(' ')[0] #if declaration match found isolate the type of the list
                    found = True #flag a declaration has been found
                else:
                    pass
            #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
            function_var = ' %s' % list_name 
            if(arg_vars != [] and found == False): #if the list of arguments for active function is not empty and a match was not yet found
                for i in range(0,len(arg_vars)): #iteratae over the arguments in the function definition
                    if(function_var not in arg_vars[i]):  #if no match for current argument pass
                        pass
                    else: #if match found isolate the type from the argument as the type for the subscript
                        type_check = arg_vars[i].split(' ')[0]
                        found = True #flag a match was found
            else:
                pass
            #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
            if(function_body != [] and found == False):
                #iterate backwards over the lines in the function body to get most recent declaration
                for i in reversed(range(0,len(function_body))):
                    declaration_check = '%s = ' % list_name #expected declaration style of the list
                    #if not match in the line do nothing
                    if(declaration_check not in converted_lines[i]):
                        pass
                    else: #if there is a match isolate the type from the declaration 
                        type_check = converted_lines[i].split(' ')[0]
                        found = True #flag as found
            #default to an auto type if no match is found
            if(found == False):
                type_check = 'auto'
            else:
                #list type will return something like vector<float>, the value from the subscript will therefore be a float
                #need to isolate what is inside the first level of angle brackets
                inner_level = type_check.split('std::vector<',1)[1] #remove the first lot of vector definition
                mirrored = inner_level[::-1].replace('>','',1) #remove the final angle bracket to completely isolate the inner level
                isolated_inner = mirrored[::-1] #re-mirror to get back original value
                type_check = isolated_inner #type is now this isolted inner value
            
            val_assign = subscript #value is the formatted subscript
        
        #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
        elif(type(name_assign) == tuple):
            #name_assign will have the style ('subscript',['index',list_name,index_val])
            args = name_assign[1] #store arguments as the first element
            list_name = args[1] #name of the list is first argument element
            type_script = args[0] #store the type of subscripting
            if(type_script == 'index'): #if index type of subscripting format subscript as list_name[index_value]
                subscript = list_name + '[%s]' % args[2]
            elif(type_script == 'slice'): #if slice type of subscripting format subscript as list_name[lower_index:upper_index]
                subscript = list_name + '[%s:%s]' % (args[2],args[3])
            else: #if not one of these two raise a type error as it's not handled yet
                raise TypeError('Subscript type not yet handled: %s' % type_script)
            #make converted line in the style list_name[index] = val;
            converted_line = subscript + ' = ' + str(val_assign) +';'
            return converted_line #return converted line as it's in different style to other assigns
        #if the type check is a tuple this is a function call return stored to a variable
        elif(type_check == tuple):
            #val assign will be equal to ('func_name',[func_args])
            func_name = val_assign[0] #store the name of the function
            val_assign = val_assign[1] #store the args of the function
            type_check = 'auto' #default to auto type returning
            args = val_assign #set args as the stored values
            for j in range(0,len(args)): #iterate over the args checking if string or variable
                args[j] = string_or_var(args[j]) #stays same if variable overwrites to be in "" if string
                
            out_args = '' #set arguments string
            for i in range(0,len(args)): #iterate over the arguments
                out_args += args[i] + ', ' #add the argument and ', ' to make comma separated string
            out_args = out_args[:-2] #remove the extra ', '
            #call formatted as func_name(func_args)
            val_assign = func_name + '(' + out_args + ')'
        
        #if val_assign is a list and a return of a binary operator string
        elif(type_check == list and val_assign[0] == 'BinOp'):
            #val_assign will be equal to ('BinOp',[list of values and operators])
            op_string = val_assign[1] #store list of arguments
            eq_string = '' #define the string of the equation
            for i in op_string: #iterate over the arguments
                eq_string += str(i) + ' ' #add the argument to the existing string
            eq_string = eq_string[:-1] #remove the extra space at the end of the argument string
            val_assign = eq_string #set val_assign to this formatted string
            
            #a BinOp string could be for a concatenation of a string instead of maths
            #so attempt to determine the type of the first variable in the equation
            found = False #flag for no match
            #iterate backwards over converted lines for most recent definition
            for i in reversed(range(0,len(converted_lines))):
                find_def = ' %s = ' %  op_string[0] #define the string to search for
                if(find_def in converted_lines[i]): #if a match on this line
                    type_check = converted_lines[i].split(' ')[0] #isolate the type from the first word of the line
                    found = True #flag that a matach has been found
                else:
                    pass
            function_var = ' %s' % op_string[0] #string to search for match in function arguments
            #only do this search if the line is in the body of a function that has not yet been completed
            if(arg_vars != [] and found == False): 
                for i in range(0,len(arg_vars)): #iterate over the arguments of the function
                    if(function_var not in arg_vars[i]): #if there is no match for this argument pass
                        pass
                    else: #if there is a match isolate the type from the first word of the argument declaration
                        type_check = arg_vars[i].split(' ')[0]
                        found = True #flag that a match has been found
            else:
                pass
            #if a function body is currently in conversion and not yet appended to converted lines iterate over it to find match
            if(function_body != [] and found == False):
                #iterate backwards through body to get most recent declaration
                for i in reversed(range(0,len(function_body))):
                    declaration_check = '%s = ' % op_string[0] #string to check for in function body
                    if(declaration_check not in converted_lines[i]): #if no match this line do nothing
                        pass
                    else: #if match was found isolate the type as first word of line
                        type_check = converted_lines[i].split(' ')[0]
                        found = True #flag as found
            else:
                pass
                
            if(found == False): #if no match was found default to an auto type
                type_check = 'auto'
            else: #if match don't overwrite
                pass
        #if the type of val_assign is a list then it is a list declaration
        elif(type_check == list):
            #val_assign could be 1D list such as [2,3,4] etc or could be list of lists
            inside_level = val_assign[0] #get the first element of the list to test if it is also a list for declaration purposes
            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)
            while(type(inside_level) == list): #if the inside level is another list
                inside_level = inside_level[0] #take another inside level
                nest_level+=1 #increase the nesting count of the list
            nest_level-=1 #remove one to compensate for overcounting the nesting level
            #for as many lists are nested repeat std::vector<, for example the list [[2,3,4],[5,6,7]]
            #here would get a type check of std::vector<std::vector<int>>
            type_check = 'std::vector<'*(nest_level+1)+str(type(inside_level))+'>'*(nest_level+1)
            val_assign = str(val_assign).replace('[','{').replace(']','}') #convert list formatting to vector formatting
        #if val_assign is a string
        elif(type_check == str): 
            #check to ensure it is not a bool value
            if(val_assign == 'true' or val_assign == 'false'):
                type_check = 'bool' #set type as bool
            else:
                #val_assign could be equal to 'Hello' for example
                val_assign = '"%s"' % val_assign #add speech marks around the value to allow string formatting
                type_check = 'std::string' #specify the type to declare as std::string
        #for any other standard type, for example val_assign = 3.3, type_check = <class 'float'>
        else: 
            pass
        #define the converted declaration as the type check (if a standard one then remove the <class ' and '> from the string of the type)
        #then the variable name and the value assigned to it, e.g. float test = 7.9
        converted_line = str(type_check).replace("<class '",'').replace("'>","") + ' %s = %s;' % (name_assign,val_assign)
 
        return converted_line #return the converted line
 
#define a parser for number nodes
class NumParser(ast.NodeVisitor):
    def visit_Num(self,node): #define function to visit the number node
        return node.n #return the number from the node
 
#define a parser for a unary operator node
class UnaryOpParser(ast.NodeVisitor):
    def visit_UnaryOp(self,node): #visit the unary operator node
        if(type(node.op) == ast.USub): #if the operator is a '-' to make a negative number
            num = NumParser().visit_Num(node.operand) #get the number (this will be the number without - operator)
            num_with_operator = -1*num #make the number negative
            return num_with_operator #return the negative number
        elif(type(node.op) == ast.UAdd): #if the operator is a '+' to make a positive number
            num = NumParser().visit_Num(node.operand) #get the number
            num_with_operator = np.abs(num) #take the absolute of it to make it positive
            return num_with_operator #return the number
 
#define a parser for string nodes
class StrParser(ast.NodeVisitor):
    def visit_Str(self,node): #visit the string node
        return node.s #return the string value
 
#define a parser for list nodes
class ListParser(ast.NodeVisitor):
    def visit_List(self,node): #visit the list node
        list_vals = [] #define list of the values
        for i in node.elts: #for each argument of the list
            #find the type and make any necessary conversions then append to the list values
            #if there is a nested list then the type will revisit this parser, e.g. list_Vals = [] to start
            #then a sub list vals is generated and values appended to it then return that sub list to
            #append to the original list_vals, e.g. [[2,3,4]]
            list_vals.append(general_access_node(i)) 
        
        return list_vals #return the completed list of values
 
#define a parser for subscript nodes (taking indices or slices of lists)
class SubscriptParser(ast.NodeVisitor):
    def visit_Subscript(self,node): #visit the subscript node
        list_slice = [] #list of parameters of the subscript to format
        name = node.value.id #the variable name the list has
        if(type(node.slice) == ast.Index): #if the type of subscripting is taking an index from the list
            index = general_access_node(node.slice.value) #get the value of the index, could be number or letter if in a loop process
            list_slice.append('index') #mark this was an index process for later conversion
            list_slice.append(name) #add the name to the parameters
            list_slice.append(index) #add the index value to the parameters
 
        elif(type(node.slice) == ast.Slice): #if type of subscripting is a slice
            lower_index = general_access_node(node.slice.lower) #get the value of the lower index of the list
            upper_index = general_access_node(node.slice.upper) #get the value of the upper index of the list
            list_slice.append('slice') #mark this was a slice for ater conversion
            list_slice.append(name) #add the name to the parameters
            list_slice.append(lower_index) #add the lower index to the parameters
            list_slice.append(upper_index) #add the upper index to the parameters
 
        else: #need to do extslice type here later
            pass #@todo ExtSlice
        
        return 'subscript',list_slice #return a marking that this is a subscript node and the values
 
#parser for binary operator nodes
class BinOpParser(ast.NodeVisitor):
    def visit_BinOp(self,node): #visit the binary operator node
        vals = [] #list for values either side of binary operator
        left_val = general_access_node(node.left) #determine the type and get the value of arguments left of the operator
        right_val = general_access_node(node.right) #determine the type and get the value of arguments right of the operator
        
        ast_ops = [ast.Add,ast.Sub,ast.Div,ast.Mult] #list of types of ast operators
        #@todo handle more operators
        c_ops = ['+','-','/','*'] #corresponding list  of C++ operators
        operator = node.op #get operator between the left and right vals
        try: #attempt to find the operator type from the ast operator list
            op_index = ast_ops.index(type(operator))
            operator = c_ops[op_index] #get the corresponding C++ operator
        except: #if no index found then raise a type error to flag that it needs handling
            raise TypeError('Binary operator type not handled yet: %s' % operator)
        vals.append(left_val) #append the left value
        vals.append(operator) #append the new C++ operator 
        vals.append(right_val) #append the right value
        return vals #return the list of values
 
#define a parser for expression nodes
class ExprParser(ast.NodeVisitor):
    def visit_Expr(self,node): #visit the expression node
        global converted_lines, function_body, arg_vars #allow access to relevant globals
        line = general_access_node(node.value) #determine type and do any conversions for the argument of the expression
        #example line = ('print',[a,b,c])
        function = line[0] #the function of the expression
        #handle different inbuilt functions to convert to C++ versions
        #@todo more of these inbuilts
        if(function == 'print'): #if the expression is a print statement
            function = 'std::cout << ' #replace the function with the std::cout function which will have at least one argument
            args = line[1] #store the arguments of the function
            #iterate over the function arguments, this is to check if the argument is a variable or a string
            for j in range(0,len(args)):
                #print(args)
                args[j] = string_or_var(args[j])
 
            out_args = '' #string of output arguments
            for i in range(0,len(args)): #iterate over the number of arguments
                out_args += args[i] + ' << ' #add the argument and ' << ' to the output string 
            out_args += 'std::endl' #add an endline to the end of the string
            
            converted_line = function + out_args + ';' #make the converted line std::cout << arg1 << arg2 ... << std::endl;
        
        elif('.' in function): #check if the function is an attribute
            #this means it is an attribute that should have already been resolved
            #an example could be line = ('g.append(',[args_list])
            args = line[1] #store the args as the first element
            for j in range(0,len(args)): #iterate over the arguments
                if(type(args[j]) == str):
                    args[j] = string_or_var(args[j])
 
                elif(type(args[j]) == list): #if the argument type is a list
                    args[j] = str(args[j]).replace('[','{').replace(']','}') #convert argument to vector notation
                else:
                    pass
            args_string = '' #define a blank string to make string of arguments
            for i in range(0,len(args)): #iterate over the arguments
                args_string += str(args[i]) + ', ' #add the argument and ', ' to the arguments string
            args_string = args_string[:-2] #remoev the extra ', ' from the end
            converted_line = '%s%s);' % (function,args_string) #complete the converted line to gie e.g. g.push_back(9.9)
        else:
            #if made it this far the expression is treated as a function call
            #example line = ('function_name',[function_args])
            args = line[1] #store the arugments of the function call
            for j in range(0,len(args)): #iterate over the arguments
                args[j] = string_or_var(args[j])
                
            out_args = '' #as before format final arguments string as comma separated
            for i in range(0,len(args)):
                out_args += args[i] + ', '
            out_args = out_args[:-2]
            #define the converted line as function_name(funtion_args);
            converted_line = function + '(' + out_args + ')' + ';'
 
        return converted_line #return the converted line  
 
#define parser to handle function call nodes
class CallParser2(ast.NodeVisitor):
    def visit_Call(self,node): #visit function call node
        func_type = general_access_node(node.func) #call to get the value of the name of the function being called
        args_list = [] #list for arguments of tbhe function
        for i in range(0,len(node.args)): #iterate over the arguments of the function
            #print(node.args[i])
            args_list.append(general_access_node(node.args[i])) #classify and extract the value of the arguments of the function
        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
            converted_func = args_list[0] + '.size()' #e.g. if it was len(a) change to a.size()
            return converted_func #return the converted function
        else:
            pass
        return func_type, args_list #if special cases not met return the type of function and arguments list
 
#define parser to handle return nodes
class ReturnParser(ast.NodeVisitor):
    def visit_Return(self,node): #visit return node
        if(type(node.value) == None): #if it is a void return with no values return None
            return node.value
        else: #if it has values get the types and values it is meant to returning and return them
            args_list = general_access_node(node.value)
            return args_list
 
#define parser to handle tuple nodes
class TupleParser(ast.NodeVisitor):
    def visit_Tuple(self,node): #visit tuple node
        args_list = [] #define list of arguments
        for i in range(0,len(node.elts)): #iterate over the values in the tuple
            args_list.append(general_access_node(node.elts[i])) #get the type and subsequant value of each argument in the tuple
        return args_list #return a list of arguments
 
#define parser to handle if statement nodes
class IfParser(ast.NodeVisitor):
    def visit_If(self,node): #visit if statement node
        global converted_lines, top_level_if #have access to relevant globals
 
        if_block = [] #make a list of the if block
        condition = general_access_node(node.test) #convert the condition of the if statement analysing the node
        condition_string = '' #make a string of the condition statement
        for i in range(0,len(condition)): #iterate over the arguments of the condition, each should have already been converted to an appropriate format
            condition_string += str(condition[i]) + ' ' #add the condition arguments space separated
        condition_string = condition_string[:-1] #remove the extra space at the end
        if(top_level_if): #if this is the first if statement
            statement = 'if (%s) {' % condition_string #format it as opening if with the condition
            top_level_if = False #next if statement is not top level, i.e. there is an elif statement
        else: #if an elif statement is already present
            statement = 'else if (%s) {' % condition_string #format with else if instead
        if_block.append(statement) #append the statement to the if block
 
        for i in node.body: #iterate over the nodes in the body of the if block
            #there could potentially be more if statements nested inside the main if statement
            #the first nested ifs would need to be if and not elif , hence reset the flag to be top level
            top_level_if = True
            line = general_access_node(i) #convert the node to an appropriate format
            if_block.append(line) #append the line to the if_block
            top_level_if = False #flag top level back to false as nesting has finished
            
        if_block.append('}') #close the if statement block
 
        #check if the else block contains another if block (this occurs if an elif statement is used)
        if(node.orelse == [] or type(node.orelse[0]) != ast.If): #if no elif statement in if block
            if_block.append('else {') #append an else statement
        else:
            pass
        
        #@todo fix where an else if appears for the first if inside an else block
        #example problem:
        #else {
        #   else if (...) {
        #   
        #   }
        #   else {
        #   
        #   }
        #}
        for i in node.orelse: #iterate over the nodes in the else statement
            try: #if its an elif statement i.test will work else will run except
                i.test
                line = general_access_node(i) #convert line
                if_block.append(line) #add line to if block
            except: #if fails deafult to top level if statement for any statement inside
                top_level_if = True #mark top level
                line = general_access_node(i) #get the type of line and convert
                if_block.append(line) #append the line to the if block
                top_level_if = False #reset top level to false
 
        #@todo figure out a better way to ensure correct number of closing braces
        if(if_block[-1][-1] == '}'): #check if all if statements got closed off
            pass
        else: #if not close off the final one
            if_block.append('}')
 
        top_level_if = True #reset top level back to true
 
        return if_block #return the converted if block
 
#define parser for compare statement nodes, inside if blocks
class CompareParser(ast.NodeVisitor):
    def visit_Compare(self,node): #visit the compare node
        left_arg = general_access_node(node.left) #store the left value of the argument
        #define the types of ast operators 
        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]
        #define the corresponding c++ operators
        c_ops = ['==','!=','<','<=','>','>=','TODO','TODO','TODO','TODO','&&','||']
        
        #@todo handle the is and in operators for c++
        full_args = [] #list of full arguments from the compare statement
        full_args.append(left_arg) #append the left value argument to full arguments
        for i in range(0,len(node.ops)): #iterate over the number of operators for long chains of comparisons
            index = ast_ops.index(type(node.ops[i])) #get the index of match of the ast_ops
            c_op = c_ops[index] #get the corresponding c_op
            full_args.append(c_op) #append the c operator to the args
            value = general_access_node(node.comparators[i]) #get the value being compared to type and value
            if(type(value) == str): #if it is a string
                value = string_or_var(value) #run check to see if it is string or variable
            else:
                pass
            full_args.append(value) #append the value to the full args
        
        return full_args #return the full args
 
#define parser for name nodes
class NameParser(ast.NodeVisitor):
    def visit_Name(self,node): #visit name node
        name = node.id #get the name id
        return name #return the name
 
#define parser for for loop nodes
class ForParser(ast.NodeVisitor):
    def visit_For(self,node): #visit the for node
        global converted_lines
        iterator = general_access_node(node.target) #get the iterator of the loop
        condition = general_access_node(node.iter) #get the condition of the loop
        try: #check for a special condition of iterating over lines in an open file
            file = False #flag for whether this loop is accessing a file
            file_check = 'std::fstream %s;' % condition #check for declaration of file
            if(type(condition) != tuple): #will only be a file declaration if condition is not a tuple
                for line in converted_lines: #iterate over converted lines to look for file declaration
                    if(type(line) == list):
                        for i in line:
                            if(i==file_check):
                                file = True #if file declaration found flag as such
                                break
                            else:
                                pass
                    else:
                        if(line==file_check):
                            file = True #if file declaration found flag as such
                            break
                        else:
                            pass
            #this condition should be true for an iterative loop of type "for line in file:"
            if(type(condition) != tuple and file==True):                
                for_condition = [] #make a block for this type of for loop
                declare_iterator = 'std::string %s;' % iterator #make iterator declaration
                new_condition = "while (!%s.eof()) {" % condition #conver to a while loop to iterate over file
                getline_string = "std::getline(%s,%s,'\\n');"% (condition,iterator) #get the line of the file
                #append relevant statements to condition block
                for_condition.append(declare_iterator) 
                for_condition.append(new_condition)
                for_condition.append(getline_string)
                for i in node.body: #for each node in the body
                    line = general_access_node(i) #classify and convert the line
                    if('std::cout << ' in line): #check if attempting to print something
                        splitup = line.split(' << ') #split on the args separator
                        for i in range(0,len(splitup)): #iterate over the split args
                            if(splitup[i] == ('"%s"' % iterator)): #check if the arg is the iterator which has falsely been converted in to a string
                                splitup[i] = iterator #if false conversion made switch out for iterator
                                line = ' << '.join(splitup) #rejoin the line
                            else: #if no false string pass
                                pass
                    elif('push_back' in line): #check if attempting to append something to a list
                        if(('push_back("%s")' % iterator) in line): #if iterator being pushed back after false conversion to a string replace the string with the iterator
                            line = line.replace(('push_back("%s")' % iterator), ('push_back(%s)' % iterator))
                        else:
                            pass
                    for_condition.append(line) #convert the node and append it to the block
                for_condition.append('}') #close the for brace
                return for_condition #return this special case loop
            else:
                pass
        except: #if fails condition is not true anyway
            pass
        #e.g. of condition = ('range'[0,list_name.size()]) or number equivalent
        if(condition[0] == 'range'):
            lower_limit = condition[1][0] #lower limit of range
            upper_limit = condition[1][1] #upper limit of range
            #write the condition of the for loop incrementing in the range
            if(upper_limit==0): #if the upper limit is 0, e.g. range(10,0)
                #make the for condition iterate backwards from the "lower" limit (with higher value) to "upper" limit (value 0)
                for_condition = 'for (int %s = %s; %s > %s; %s--) {' % (iterator,lower_limit,iterator,upper_limit,iterator)
            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
                #make the for condition iterate backwards from the "lower" limit (with higher value) to "upper" limit (lower value)
                for_condition = 'for (int %s = %s; %s > %s; %s--) {' % (iterator,lower_limit,iterator,upper_limit,iterator)
            else: #otherwise assume forwards iteration
                for_condition = 'for (int %s = %s; %s < %s; %s++) {' % (iterator,lower_limit,iterator,upper_limit,iterator)
        elif(condition[0] == 'reversed'):
            #condition will be in format ('reversed',[('range',[0,5])])
            #take limits opposite way round as they are in a reversed function
            upper_limit = condition[1][0][1][0]
            lower_limit = condition[1][0][1][1]
            if(upper_limit==0): #conditions are as above but reversed
                for_condition = 'for (int %s = %s; %s > %s; %s--) {' % (iterator,lower_limit,iterator,upper_limit,iterator)
            elif(isinstance(upper_limit,int) and isinstance(lower_limit,int) and lower_limit>upper_limit): 
                for_condition = 'for (int %s = %s; %s > %s; %s--) {' % (iterator,lower_limit,iterator,upper_limit,iterator)
            else:
                for_condition = 'for (int %s = %s; %s < %s; %s++) {' % (iterator,lower_limit,iterator,upper_limit,iterator)
        else: #if line was for x in list_name, the condition will be (list_name)
            vector = condition
            #format for condition
            for_condition = 'for (auto %s: %s) {' % (iterator,vector)
 
        body_block = [] #define body of for loop
        body_block.append(for_condition) #append the for condition to the body
        for i in node.body: #for each node in the body
            line = general_access_node(i) #classify and convert the line
            if('std::cout << ' in line): #check if attempting to print something
                splitup = line.split(' << ') #split on the args separator
                for i in range(0,len(splitup)): #iterate over the split args
                    if(splitup[i] == ('"%s"' % iterator)): #check if the arg is the iterator which has falsely been converted in to a string
                        splitup[i] = iterator #if false conversion made switch out for iterator
                        line = ' << '.join(splitup) #rejoin the line
                    else: #if no false string pass
                        pass
            elif('push_back' in line):
                if(('push_back("%s")' % iterator) in line):
                    line = line.replace(('push_back("%s")' % iterator), ('push_back(%s)' % iterator))
                else:
                    pass
            body_block.append(line) #convert the node and append it to the block
        body_block.append('}') #close the for brace
        return body_block #return the body
 
#define parser for attribute nodes
class AttributeParser(ast.NodeVisitor):
    def visit_Attribute(self,node): #visit the attribute node
        attribute = node.attr #gives the function being applied, e.g for a.append, returns append
        value = general_access_node(node.value) #classify and convert the object being appended
        #print(attribute,value)
        if(attribute=='append'): #if the atrribute is append
            attribute = 'push_back' #replace with vector push_back method
        elif(value=='self'): #if not raise a type error to flag this attribute is not yet handled
            #converted_line = '%s.%s' % (value,attribute)
            return attribute
            #return None
            #raise TypeError('Attribute type not handled yet: %s,%s' % (attribute,value))
        else:
            pass
        
        converted_line = '%s.%s(' % (value,attribute) #define the converted line
        return converted_line #return the converted line
 
#define parser for while nodes
class WhileParser(ast.NodeVisitor):
    def visit_While(self,node): #visit while node
        while_body = [] #define while block
        condition = general_access_node(node.test) #convert the while condition
        condition_string = '' #define string for the condition
        for i in condition: #iterate over the elements in the condition list
            condition_string+=str(i)+' ' #add the condition element and a space
        condition_string = condition_string[:-1] #remove the extra space
        condition_line = 'while (%s) {' % condition_string #define the while statement
        while_body.append(condition_line) #append the while statement to the block
        for i in node.body: #iterate over nodes in the body of the while loop
            line = general_access_node(i) #classify and convert the node
            while_body.append(line) #append the converted line to the block
        while_body.append('}') #close the while loop
        #@todo handle the orelse node
        return while_body #return the while block
 
#define parser for AugAssign nodes
class AugAssignParser(ast.NodeVisitor):
    def visit_AugAssign(self,node): #visit AugAssign nodes
        var = general_access_node(node.target) #get the variable value is assigned to
        ast_operators = [ast.Add,ast.Sub,ast.Div,ast.Mult] #define list of ast operators
        c_ops = ['+=','-=','/=','*='] #define corresponding list of c++ operators
        index = ast_operators.index(type(node.op)) #get the index matching the operator
        operator = c_ops[index] #get the correspoding c++ operator to the matched operator
        value = general_access_node(node.value) #classify and convert the value node
        converted_line = '%s %s %s;' % (var,operator,value) #format the aug assign string
        return converted_line #return the converted line
 
#define parser for name constant nodes
class NameConstantParser(ast.NodeVisitor):
    def visit_NameConstant(self,node): #visit name constant node
        #node.value should be True False or None
        if(node.value == True): #if True return C++ bool true
            return 'true'
        elif(node.value == False): #if false return C++ bool false
            return 'false'
        else: #if none flag not handled yet
            raise TypeError('NameConstant not true or false and not handled : %s' % node.value)
 
#define function to check if a value is a string or a variable as they are classified the same by the AST
def string_or_var(value):
    global converted_lines, arg_vars, function_body, class_args #have access to relevant globals
    found = False #flag no match found
    #iterate backwards over converted lines looking for a match, backwards to get most recent definition
    for i in reversed(range(0,len(converted_lines))):
        declaration_check = '%s = ' % value #look for declaration of variable
        if(declaration_check not in converted_lines[i]): #if not match pass
            pass
        else: #if match flag a match found
            found = True
            break
    #if there is a function under conversion not yet appended and no match found
    if(arg_vars != [] and found == False):
        for i in range(0,len(arg_vars)): #iterate over the function arguments
            if(value not in arg_vars[i]): #if no match pass
                pass
            else: #if match flag a match was found
                found = True
                break
    else:
        pass
    #if there is a function under conversion not yet appended and no match found
    if(function_body != [] and found == False):
        for i in reversed(range(0,len(function_body))): #iterate backwards over function body to get most recent definition
            declaration_check = '%s = ' % value #look for declaration of variable
            if((converted_lines==[]) or declaration_check not in converted_lines[i]): #if no match pass
                pass
            else: #if match flag that a match was found
                found = True
                break
            
    #if there is an active class check for a match in its declarated arguments
    if(class_args != [] and found == False):
        for i in range(0,len(class_args)): #iterate over class args to check for match
            declaration_check = '%s' % value #look for declaration of variable
            if((class_args==[]) or declaration_check not in class_args[i]): #if no match pass
                pass
            else: #if match flag that a match was found
                found = True
                break
    
    
    if(found==False):
        #print(converted_lines)
        second_declare = ' %s;' % value
        for i in reversed(range(0,len(converted_lines))):
            for j in range(0,len(converted_lines[i])):
                if(second_declare not in converted_lines[i][j]): #if not match pass
                    pass
                else: #if match flag a match found
                    found = True
                    break
    
    if(found == False and value != 'true' and value != 'false'): #if no match default to string
        value = '"%s"' % value
    else:
        pass
    #if match will return the same value therefore a variable, if no match it will put speech marks around for c++ string definition
    return value
 
#this functions purpose is to receive any node and determine it's type
#once determined the node will be passed to an appropriate parsing function or directly
#handle the node for some simple cases then return the value of the node after it has
#been parsed and converted by the parser it sent it to
def general_access_node(node):
    #check the type of the node and compare to currently handled type
    if(type(node) == ast.FunctionDef):
        #store the value of the return from the parsed node after sending it to be decoded
        parsed_node = FunctionParser().visit_FunctionDef(node)
    elif(type(node) == ast.Assign):
        parsed_node = AssignParser().visit_Assign(node)
    elif(type(node) == ast.Expr):
        parsed_node = ExprParser().visit_Expr(node)
    elif(type(node) == ast.If):
        parsed_node = IfParser().visit_If(node)
    elif(type(node) == ast.For):
        parsed_node = ForParser().visit_For(node)
    elif(type(node) == ast.Num):
        parsed_node = NumParser().visit_Num(node)
    elif(type(node) == ast.Str):
        parsed_node = StrParser().visit_Str(node)
    elif(type(node) == ast.UnaryOp):
        parsed_node = UnaryOpParser().visit_UnaryOp(node)
    elif(type(node) == ast.Subscript):
        parsed_node = SubscriptParser().visit_Subscript(node)
    elif(type(node) == ast.Call):
        parsed_node = CallParser2().visit_Call(node)
    elif(type(node) == ast.Return):
        parsed_node = ReturnParser().visit_Return(node)
    elif(type(node) == ast.Tuple):
        parsed_node = TupleParser().visit_Tuple(node)
    elif(type(node) == ast.List):
        parsed_node = ListParser().visit_List(node)
    elif(type(node) == ast.Compare):
        parsed_node = CompareParser().visit_Compare(node)
    elif(type(node) == ast.Name):
        parsed_node = NameParser().visit_Name(node)
    elif(type(node) == ast.Pass):
        parsed_node = '\n'
    elif(type(node) == ast.Attribute):
        parsed_node = AttributeParser().visit_Attribute(node)
    elif(type(node) == str):
        parsed_node = node
    elif(type(node) == ast.While):
        parsed_node = WhileParser().visit_While(node)
    elif(type(node) == ast.AugAssign):
        parsed_node = AugAssignParser().visit_AugAssign(node)
    elif(type(node) == ast.NameConstant):
        parsed_node = NameConstantParser().visit_NameConstant(node)
    elif(type(node) == ast.ClassDef):
        parsed_node = ClassDefParser().visit_ClassDef(node)
    elif(type(node) == ast.Break):
        parsed_node = 'break;'
    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
        raise TypeError('Parser not found for type: %s' % type(node))
    
    return parsed_node #return the parsed/converted node value
 
#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
def main(script_to_parse,script_of_function_calls=None):
    global converted_lines, list_types #make globals of the converted lines and function argument types
    list_types = [] #define list of function types from script of function calls
    converted_lines = [] #define list of converted C++ lines
    #if a script of cuntion calls has been provided analyse it for types, if not then no functions are defined in the python script
    if(script_of_function_calls!=None):
        file2 = open(script_of_function_calls,'r').read() #open and read the script of function calls specified
        call_parse = ast.parse(file2) #parse the script to make an AST of nodes
        for node in call_parse.body: #iterate over the nodes in the body of the tree
            funcs_args = [] #define list of the arguments of current function in the tree
            for arg in node.value.args: #iterate over the arguments in the function currently active
                arg_val = general_access_node(arg) #get the value of the argument 
                #run through a series of checks to determine the type of the argument provided
                #once a match is found append the appropriate type to the current function's arguments type list
                if isinstance(arg_val,int):
                    funcs_args.append('int')
                elif isinstance(arg_val,float):
                    funcs_args.append('float')
                elif isinstance(arg_val,str):
                    funcs_args.append('std::string')
                elif isinstance(arg_val,list): #if the argument is a list it needs special handling
                    inside_level = arg_val[0] #get the first element of the list to check if it contains more lists
                    nest_level = 1 #increase the nest level by one
                    if isinstance(inside_level,list): #if the first element is a list
                        inside_level = inside_level[0] #take the first element of the sub list
                        nest_level+=1 #increase the nest level by one
                        while(isinstance(inside_level,list)): #if still a sub list then repeat this process until the first non list element is found
                            inside_level = inside_level[0]
                            nest_level+=1
                        type_check = type(inside_level) #get the type of the first non list element
                        
                    else: #if not a list of lists get the type of the first element
                        type_check = type(inside_level)
                    #define the type of list, nest level is used to determine the number of vector commands to nest
                    #for example the list [[2,3,4],[5,6,7]] would have nest_level = 2 and type_check = <class 'int'>
                    #so the below code would format type_list = <std::vector<std::vector<int>>
                    type_list = ('std::vector<'*nest_level)+str(type_check).replace("<class '",'').replace("'>",'') + ('>'*nest_level)
                    funcs_args.append(type_list) #append this vector definition to the function's arguments
            list_types.append(funcs_args) #append the completed funtion arguments type list to the list of type lists
    else: #if no function calls skip this process
        pass
    
    file = open(script_to_parse,'r').read() #open the python file to convert and read it
    tree = ast.parse(file) #make an AST of the nodes within the file
    main = False #flag that a main function has not yet been added to the converted script
    for node in tree.body: #iterate over the nodes in the body of the AST
        line_test = general_access_node(node) #run function to determine the type of the node and convert it accordingly
        #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
        #print(line_test[0])
        if(('auto' in line_test[0] and '{' in line_test[0]) or ('class' in line_test[0] and '{' in line_test[0])):
            pass
        else: #if it is not a function definition
            if(main==False): #check if a main has been added yet
                converted_lines.append('int main () {') #if no main start the main function here as function definitions have finished
                main=True #flag that a main has been added
            else: #if main has been added do not add it again
                pass
        #check to see if the returned value has already been added to the converted lines and the return was not a void one
        #if it is not a NoneType return and has not yet been added to converted lines then add it
        #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
        #if(line_test != converted_lines[len(converted_lines)-1] and line_test != None):
        converted_lines.append(line_test)
        #else: #if it has been addded or is NoneType do nothing
         #   pass
    converted_lines.append('return 0;') #add a return for the c++ main function
    converted_lines.append('}') #close the main function
    #below are checks to see what include statements are needed for the c++ code
    #each one will chec for an instance of a function and if a match is found the appropriate
    #include statement will be inserted in to the top of the converted lines list
    if(('std::cout' in line for line in converted_lines) or ('std::cin' in line for line in converted_lines)):
        converted_lines.insert(0,'#include <iostream>')
    else:
        pass
    if('std::string' in line for line in converted_lines):
        converted_lines.insert(0,'#include <string>')
    else:
        pass
    if('std::vector' in line for line in converted_lines):
        converted_lines.insert(0,'#include <vector>')
    else:
        pass
    if('std::fstream' in line for line in converted_lines):
        converted_lines.insert(0,'#include <fstream>')
    else:
        pass
    return converted_lines #return the list of converted c++ lines
 
#function to check if line is list or list of lists and convert in to flattened data
def walk(e):
    if(type(e) == list): #if the line is a list
        for v2 in e: #iterate over list elements
            for v3 in walk(v2): #iterate over sub list elements (which will iterate further if another sublist)
                yield v3 #yield the non list element
    else: #if line not a list
        yield e #return the line
 
#function to write the parsed data to an output .cpp file
def write_file(data,name_of_output='Output.cpp'):
    file = open(name_of_output,'w+') #open an output file to the specified path for writing/creation
    indentation_level=0 #default indentation level is 0
    public_open = False
    flatten = [] #create a list of flattened line data
    for line in walk(data): #call walk function on converted data
        flatten.append(line) #append the flattened line to the flattened data
 
    for line in flatten: #iterate over the lines in the flattened data
        #print(line,indentation_level)
        if(public_open==True and ('auto' in line or '};' in line)):
            indentation_level-=1
            public_open=False
        else:
            pass
        open_brace_count = line.count('{') #count number of open brackets on the line
        close_brace_count = line.count('}') #count number of closing brackets on the line
        if(open_brace_count>close_brace_count):
            #if more open brackets than close, the code following (not including) this line
            #will require indentation, as such write this line and the increase the indentation level for subsequent lines
            file.write(('\t'*indentation_level)+line+'\n')
            indentation_level+=1
        elif(open_brace_count<close_brace_count):
            #if more close brackets than open brackets, the code following is outside of the
            #function block, as such will need indentation level reduced by one, this includes the closing brace this time
            #hence the reverse order to the condition above
            indentation_level-=1
            file.write(('\t'*indentation_level)+line+'\n')
        elif('public:' in line):
            file.write(('\t'*indentation_level)+line+'\n')
            indentation_level+=1
            public_open = True
        else: #if number of braces equal then do not change indentation level
            file.write(('\t'*indentation_level)+line+'\n')
 
    file.close() #writing completed so close the file
 
if __name__ == '__main__':
    top_level_if = True #flag for if statments, method needs revision
    list_spiel = True #flag for displaying extra information about empty lists
    class_vars_for_call = []
    called_objs = []
    print('Beginning Parsing') #inform user parsing has began, precaution incase a large file takes a long time parsing
    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
    print('Parsing Completed') #inform user parsing has finished
    print('Writing File') #inform user output to file has started
    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
    print('Writing Completed') #inform user writeout completed

Comments