View difference between Paste ID: XPtTgMj8 and RurmzEu5
SHOW: | | - or go back to the newest paste.
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