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 |