Coverage for skema/program_analysis/CAST/pythonAST/py_ast_to_cast.py: 87%
1033 statements
« prev ^ index » next coverage.py v7.5.0, created at 2024-04-30 17:15 +0000
« prev ^ index » next coverage.py v7.5.0, created at 2024-04-30 17:15 +0000
1import ast
2from enum import unique
3import os
4import copy
5import sys
6from functools import singledispatchmethod
8from skema.utils.misc import uuid
9from skema.program_analysis.CAST2FN.model.cast import (
10 AstNode,
11 Assignment,
12 Attribute,
13 Call,
14 FunctionDef,
15 CASTLiteralValue,
16 Loop,
17 ModelBreak,
18 ModelContinue,
19 ModelIf,
20 ModelReturn,
21 ModelImport,
22 Module,
23 Name,
24 Operator,
25 RecordDef,
26 ScalarType,
27 StructureType,
28 SourceRef,
29 SourceCodeDataType,
30 VarType,
31 Var,
32 ValueConstructor,
33)
34from skema.program_analysis.CAST.pythonAST.modules_list import (
35 BUILTINS,
36 find_std_lib_module,
37)
39from skema.program_analysis.tree_sitter_parsers.build_parsers import INSTALLED_LANGUAGES_FILEPATH
41def get_python_version():
42 """
43 get_python_version gets the current version of Python that
44 is running when this script is executed
45 The expect version is 3.8.2 as that is what my (Tito) version
46 of Python is when running this pipeline
47 The plan is to eventually move onto the latest version (3.11?)
48 But 3.8.2 is the current stable version for generation
49 The latest version of Python makes some changes that this
50 pipeline still needs to adjust for
51 """
52 major = sys.version_info.major
53 minor = sys.version_info.minor
54 micro = sys.version_info.micro
56 string_version = f"{major}.{minor}.{micro}"
58 return string_version
61def merge_dicts(source, destination):
62 """merge_dicts
63 Helper function to isolate the work of merging two dictionaries by merging
64 key : value pairs from source into curr_scope
65 The merging is done 'in_place'. That is, after the function is done, destination
66 is updated with any new key : value pairs that weren't in there before.
68 Args:
69 source (dict): Dictionary of name : ID pairs
70 destination (dict): Dictionary of name : ID pairs
71 """
72 for k in source.keys():
73 if k not in destination.keys():
74 destination[k] = source[k]
77def construct_unique_name(attr_name, var_name):
78 """Constructs strings in the form of
79 "attribute.var"
80 where 'attribute' is either
81 - the name of a module
82 - an object
84 Returns:
85 string: A string representing a unique name
87 """
88 return f"{attr_name}.{var_name}"
90def check_expr_str(ast_node):
91 """
92 Checks if this node is an Expr node
93 with a string value
94 This is added to remove multi-line comments
95 that otherwise don't do anything
97 Current cases for multi-line comments
98 - FunctionDef
99 - Module level
100 - ClassDef level
101 """
103 if isinstance(ast_node, ast.Expr):
104 return isinstance(ast_node.value, ast.Constant) and isinstance(ast_node.value.value, str)
105 return False
108def get_node_name(ast_node):
109 if isinstance(ast_node, ast.Assign):
110 return get_node_name(ast_node.targets[0])
111 # return [ast_node[0].id]
112 elif isinstance(ast_node, ast.Name):
113 return [ast_node.id]
114 elif isinstance(ast_node, ast.Attribute):
115 return get_node_name(ast_node.value)
116 elif isinstance(ast_node, ast.Tuple):
117 elements = []
118 for elem in ast_node.elts:
119 elements.extend(get_node_name(elem))
120 return elements
121 elif isinstance(ast_node, Attribute):
122 return [ast_node.attr.name]
123 elif isinstance(ast_node, Var):
124 if isinstance(ast_node.val, Call):
125 return get_node_name(ast_node.val.func)
126 else:
127 return [ast_node.val.name]
128 elif isinstance(ast_node, Assignment):
129 return get_node_name(ast_node.left)
130 elif isinstance(ast_node, ast.Subscript):
131 return get_node_name(ast_node.value)
132 elif isinstance(ast_node, ast.Call):
133 return get_node_name(ast_node.func)
134 elif (
135 isinstance(ast_node, CASTLiteralValue)
136 and (ast_node.value_type == StructureType.LIST or ast_node.value_type == StructureType.TUPLE)
137 ):
138 names = []
139 for e in ast_node.value:
140 names.extend(get_node_name(e))
141 return names
142 else:
143 raise TypeError(f"Type {type(ast_node)} not supported")
146def get_op(operator):
147 ops = {
148 ast.Add: 'ast.Add',
149 ast.Sub: 'ast.Sub',
150 ast.Mult: 'ast.Mult',
151 ast.Div: 'ast.Div',
152 ast.FloorDiv: 'ast.FloorDiv',
153 ast.Mod: 'ast.Mod',
154 ast.Pow: 'ast.Pow',
155 ast.LShift: 'ast.LShift',
156 ast.RShift: 'ast.RShift',
157 ast.BitOr: 'ast.BitOr',
158 ast.BitAnd: 'ast.BitAnd',
159 ast.BitXor: 'ast.BitXor',
160 ast.And: 'ast.And',
161 ast.Or: 'ast.Or',
162 ast.Eq: 'ast.Eq',
163 ast.NotEq: 'ast.NotEq',
164 ast.Lt: 'ast.Lt',
165 ast.LtE: 'ast.LtE',
166 ast.Gt: 'ast.Gt',
167 ast.GtE: 'ast.GtE',
168 ast.In: 'ast.In',
169 ast.NotIn: 'ast.NotIn',
170 ast.UAdd: 'ast.UAdd',
171 ast.USub: 'ast.USub',
172 ast.Not: 'ast.Not',
173 ast.Invert: 'ast.Invert',
174 }
176 if type(operator) in ops.keys():
177 return ops[type(operator)]
178 return str(operator)
181class PyASTToCAST:
182 """Class PyASTToCast
183 This class is used to convert a Python program into a CAST object.
184 In particular, given a PyAST object that represents the Python program's
185 Abstract Syntax Tree, we create a Common Abstract Syntax Tree
186 representation of it.
187 Most of the functions involve visiting the children
188 to generate their CAST, and then connecting them to their parent to form
189 the parent node's CAST representation.
190 The functions, in most cases, return lists containing their generated CAST.
191 This is because in many scenarios (like in slicing) we need to return multiple
192 values at once, since multiple CAST nodes gt generated. Returning lists allows us to
193 do this, and as long as the visitors handle the data correctly, the CAST will be
194 properly generated.
195 All the visitors retrieve line number information from the PyAST nodes, and
196 include the information in their respective CAST nodes, with the exception
197 of the Module CAST visitor.
199 This class inherits from ast.NodeVisitor, to allow us to use the Visitor
200 design pattern to visit all the different kinds of PyAST nodes in a
201 similar fashion.
203 Current Fields:
204 - Aliases
205 - Visited
206 - Filenames
207 - Classes
208 - Var_Count
209 - global_identifier_dict
210 """
212 def __init__(self, file_name: str, legacy: bool = False):
213 """Initializes any auxiliary data structures that are used
214 for generating CAST.
215 The current data structures are:
216 - aliases: A dictionary used to keep track of aliases that imports use
217 (like import x as y, or from x import y as z)
218 - visited: A list used to keep track of which files have been imported
219 this is used to prevent an import cycle that could have no end
220 - filenames: A list of strings used as a stack to maintain the current file being
221 visited
222 - module_stack: A list of Module PyAST nodes used as a stack to maintain the current module
223 being visited.
224 - classes: A dictionary of class names and their associated functions.
225 - var_count: An int used when CAST variables need to be generated (i.e. loop variables, etc)
226 - global_identifier_dict: A dictionary used to map global variables to unique identifiers
227 - legacy: A flag used to determine whether we generate old style CAST (uses strings for function def names)
228 or new style CAST (uses Name CAST nodes for function def names)
229 - generated_fns: A list that holds any generated CAST Function Defs. Currently used for list/dict comprehensions
230 and lambda functions
231 - "*_count": Identifier numbers used for list/dict comprehensions, and lambda functions
232 """
234 self.aliases = {}
235 self.visited = set()
236 self.filenames = [file_name.split(".")[0]]
237 self.module_stack = []
238 self.classes = {}
239 self.var_count = 0
240 self.global_identifier_dict = {}
241 self.id_count = 0
242 self.legacy = legacy
243 self.generated_fns = []
244 self.list_comp_count = 0
245 self.dict_comp_count = 0
246 self.lambda_count = 0
248 self.curr_func_args = []
250 def insert_next_id(self, scope_dict: dict, dict_key: str):
251 """Given a scope_dictionary and a variable name as a key,
252 we insert a new key_value pair for the scope dictionary
253 The ID that we inserted gets returned because some visitors
254 need the ID for some additional work. In the cases where the returned
255 ID isn't needed it gets ignored.
257 Args:
258 scope_dict (Dict): _description_
259 dict_key (str): _description_
260 """
261 new_id_to_insert = self.id_count
262 scope_dict[dict_key] = new_id_to_insert
263 self.id_count += 1
264 return new_id_to_insert
266 def insert_alias(self, originString, alias):
267 """Inserts an alias into a dictionary that keeps track of aliases for
268 names that are aliased. For example, the following import
269 import numpy as np
270 np is an alias for the original name numpy
272 Args:
273 original (String): The original name that is being aliased
274 alias (String): The alias of the original name
275 """
276 # TODO
277 pass
279 def check_alias(self, name):
280 """Given a python string that represents a name,
281 this function checks to see if that name is an alias
282 for a different name, and returns it if it is indeed an alias.
283 Otherwise, the original name is returned.
284 """
285 if name in self.aliases:
286 return self.aliases[name]
287 else:
288 return name
290 def identify_piece(
291 self,
292 piece: AstNode,
293 prev_scope_id_dict,
294 curr_scope_id_dict,
295 ):
296 """This function is used to 'centralize' the handling of different node types
297 in list/dictionary/set comprehensions.
298 Take the following list comprehensions as examples
299 L = [ele**2 for small_l in d for ele in small_l] -- comp1
300 L = [ele**2 for small_l in foo.bar() for ele in small_l] -- comp2
301 L = [ele**2 for small_l in foo.baz for ele in small_l] -- comp3
302 F1 F2 F3 F4 F5
304 In these comprehensions F3 has a different type for its node
305 - In comp1 it's a list
306 - In comp2 it's an attribute of an object with a function call
307 - In comp3 it's an attribute of an object without a function call
309 The code that handles comprehensions generates slightly different AST depending
310 on what type these fields (F1 through F5) are, but this handling becomes very repetitive
311 and difficult to maintain if it's written in the comprehension visitors. Thus, this method
312 is to contain that handling in one place. This method acts on one field at a time, and thus will
313 be called multiple times per comprehension as necessary.
315 Args:
316 piece (AstNode): The current Python AST node we're looking at, generally an individual field
317 of the list comprehension
318 prev_scope_id_dict (Dict): Scope dictionaries in case something needs to be accessed or changed
319 curr_scope_id_dict (Dict): see above
321 [ELT for TARGET in ITER]
322 F1 F2 F3
323 F1 - doesn't need to be handled here because that's just code that is done somewhere else
324 F2/F4 - commonly it's a Name or a Tuple node
325 F3/F5 - generally a list, or something that gives back a list like:
326 * a subscript
327 * an attribute of an object with or w/out a function call
328 """
329 if isinstance(piece, ast.Tuple): # for targets (generator.target)
330 return piece
331 elif isinstance(piece, ast.Name):
332 ref = [
333 self.filenames[-1],
334 piece.col_offset,
335 piece.end_col_offset,
336 piece.lineno,
337 piece.end_lineno,
338 ]
339 # return ast.Name(id=piece.id, ctx=ast.Store(), col_offset=None, end_col_offset=None, lineno=None, end_lineno=None)
340 return ast.Name(
341 id=piece.id,
342 ctx=ast.Store(),
343 col_offset=ref[1],
344 end_col_offset=ref[2],
345 lineno=ref[3],
346 end_lineno=ref[4],
347 )
348 elif isinstance(piece, ast.Subscript): # for iters (generator.iter)
349 return piece.value
350 elif isinstance(piece, ast.Call):
351 return piece.func
352 else:
353 return piece
355 def find_function(module_node: ast.Module, f_name: str):
356 """Given a PyAST Module node, we search for a particular FunctionDef node
357 which is given to us by its function name f_name.
359 This function searches at the top level, that is it only searches FunctionDefs that
360 exist at the module level, and will not search deeper for functions within functions.
361 """
362 for stmt in module_node.body:
363 if isinstance(stmt, ast.FunctionDef) and stmt.name == f_name:
364 return stmt
366 return None
368 def create_cond(self, node, prev_scope_id_dict, curr_scope_id_dict):
369 """Used in while and if statement creation.
370 This function determines what kind of conditional we have for an if statement or a while loop
371 Either the conditional explicitly checks a value using a comparison operator or it doesnt
372 In the case that it doesn't explicitly then we have to add in an explicit check
373 """
374 ref = [
375 SourceRef(
376 source_file_name=self.filenames[-1],
377 col_start=node.col_offset,
378 col_end=node.end_col_offset,
379 row_start=node.lineno,
380 row_end=node.end_lineno,
381 )
382 ]
383 test_cond = self.visit(
384 node.test, prev_scope_id_dict, curr_scope_id_dict
385 )[0]
386 bool_func = "bool"
387 source_code_data_type = ["Python", "3.8", str(type(True))]
388 true_val = CASTLiteralValue(
389 ScalarType.BOOLEAN,
390 "True",
391 source_code_data_type=source_code_data_type,
392 source_refs=ref,
393 )
394 if isinstance(node.test, (ast.Name, ast.Constant)):
395 if bool_func not in prev_scope_id_dict.keys():
396 # If a built-in is called, then it gets added to the global dictionary if
397 # it hasn't been called before. This is to maintain one consistent ID per built-in
398 # function
399 if bool_func not in self.global_identifier_dict.keys():
400 self.insert_next_id(self.global_identifier_dict, bool_func)
402 prev_scope_id_dict[bool_func] = self.global_identifier_dict[
403 bool_func
404 ]
405 bool_call = Call(
406 func=Name(
407 "bool", id=prev_scope_id_dict[bool_func], source_refs=ref
408 ),
409 arguments=[test_cond],
410 source_refs=ref,
411 )
412 test = [Operator(source_language="Python",
413 interpreter="Python",
414 version=get_python_version(),
415 op="ast.Eq",
416 operands=[bool_call,true_val],
417 source_refs=ref)]
419 elif isinstance(node.test, ast.UnaryOp) and isinstance(
420 node.test.operand, (ast.Name, ast.Constant, ast.Call)
421 ):
422 if bool_func not in prev_scope_id_dict.keys():
423 # If a built-in is called, then it gets added to the global dictionary if
424 # it hasn't been called before. This is to maintain one consistent ID per built-in
425 # function
426 if bool_func not in self.global_identifier_dict.keys():
427 self.insert_next_id(self.global_identifier_dict, bool_func)
429 prev_scope_id_dict[bool_func] = self.global_identifier_dict[
430 bool_func
431 ]
432 bool_call = Call(
433 func=Name(
434 "bool", id=prev_scope_id_dict[bool_func], source_refs=ref
435 ),
436 arguments=[test_cond],
437 source_refs=ref,
438 )
439 test = [Operator(source_language="Python",
440 interpreter="Python",
441 version=get_python_version(),
442 op="ast.Eq",
443 operands=[bool_call,true_val],
444 source_refs=ref)]
446 else:
447 test = test_cond
449 return test
451 @singledispatchmethod
452 def visit(
453 self, node: AstNode, prev_scope_id_dict, curr_scope_id_dict
454 ):
455 # print(f"Trying to visit a node of type {type(node)} but a visitor doesn't exist")
456 # if(node != None):
457 # print(f"This is at line {node.lineno}")
458 pass
460 @visit.register
461 def visit_JoinedStr(
462 self,
463 node: ast.JoinedStr,
464 prev_scope_id_dict,
465 curr_scope_id_dict,
466 ):
467 # print("JoinedStr not generating CAST yet")
468 str_pieces = []
469 ref = [
470 SourceRef(
471 source_file_name=self.filenames[-1],
472 col_start=node.col_offset,
473 col_end=node.end_col_offset,
474 row_start=node.lineno,
475 row_end=node.end_lineno,
476 )
477 ]
478 for s in node.values:
479 source_code_data_type = ["Python", "3.8", str(type("str"))]
480 if isinstance(s, ast.Str):
481 str_pieces.append(
482 CASTLiteralValue(
483 StructureType.LIST, s.s, source_code_data_type, ref
484 )
485 )
486 else:
487 f_string_val = self.visit(
488 s.value, prev_scope_id_dict, curr_scope_id_dict
489 )
490 str_pieces.append(
491 CASTLiteralValue(
492 StructureType.LIST,
493 f_string_val,
494 source_code_data_type,
495 ref,
496 )
497 )
499 unique_name = construct_unique_name(self.filenames[-1], "Concatenate")
500 if unique_name not in prev_scope_id_dict.keys():
501 # If a built-in is called, then it gets added to the global dictionary if
502 # it hasn't been called before. This is to maintain one consistent ID per built-in
503 # function
504 if unique_name not in self.global_identifier_dict.keys():
505 self.insert_next_id(self.global_identifier_dict, unique_name)
507 prev_scope_id_dict[unique_name] = self.global_identifier_dict[
508 unique_name
509 ]
510 return [
511 Call(
512 func=Name(
513 "Concatenate",
514 id=prev_scope_id_dict[unique_name],
515 source_refs=ref,
516 ),
517 arguments=str_pieces,
518 source_refs=ref,
519 )
520 ]
522 @visit.register
523 def visit_GeneratorExp(
524 self,
525 node: ast.GeneratorExp,
526 prev_scope_id_dict,
527 curr_scope_id_dict,
528 ):
529 ref = [
530 node.col_offset,
531 node.end_col_offset,
532 node.lineno,
533 node.end_lineno,
534 ]
535 to_visit = ast.ListComp(
536 elt=node.elt,
537 generators=node.generators,
538 lineno=ref[2],
539 col_offset=ref[0],
540 end_lineno=ref[3],
541 end_col_offset=ref[1],
542 )
544 return self.visit(to_visit, prev_scope_id_dict, curr_scope_id_dict)
546 @visit.register
547 def visit_Delete(
548 self,
549 node: ast.Delete,
550 prev_scope_id_dict,
551 curr_scope_id_dict,
552 ):
553 # print("Delete not generating CAST yet")
554 source_code_data_type = ["Python", "3.8", "List"]
555 ref = [
556 SourceRef(
557 source_file_name=self.filenames[-1],
558 col_start=node.col_offset,
559 col_end=node.end_col_offset,
560 row_start=node.lineno,
561 row_end=node.end_lineno,
562 )
563 ]
564 return [
565 CASTLiteralValue(
566 StructureType.LIST,
567 "NotImplemented",
568 source_code_data_type,
569 ref,
570 )
571 ]
573 @visit.register
574 def visit_Ellipsis(
575 self,
576 node: ast.Ellipsis,
577 prev_scope_id_dict,
578 curr_scope_id_dict,
579 ):
580 source_code_data_type = ["Python", "3.8", "Ellipsis"]
581 ref = [
582 SourceRef(
583 source_file_name=self.filenames[-1],
584 col_start=node.col_offset,
585 col_end=node.end_col_offset,
586 row_start=node.lineno,
587 row_end=node.end_lineno,
588 )
589 ]
590 return [
591 CASTLiteralValue(
592 ScalarType.ELLIPSIS, "...", source_code_data_type, ref
593 )
594 ]
596 @visit.register
597 def visit_Slice(
598 self,
599 node: ast.Slice,
600 prev_scope_id_dict,
601 curr_scope_id_dict,
602 ):
603 # print("Slice not generating CAST yet")
604 source_code_data_type = ["Python", "3.8", "List"]
605 ref = [
606 SourceRef(
607 source_file_name=self.filenames[-1],
608 col_start=-1,
609 col_end=-1,
610 row_start=-1,
611 row_end=-1,
612 )
613 ]
614 return [
615 CASTLiteralValue(
616 StructureType.LIST,
617 "NotImplemented",
618 source_code_data_type,
619 ref,
620 )
621 ]
623 @visit.register
624 def visit_ExtSlice(
625 self,
626 node: ast.ExtSlice,
627 prev_scope_id_dict,
628 curr_scope_id_dict,
629 ):
630 # print("ExtSlice not generating CAST yet")
631 source_code_data_type = ["Python", "3.8", "List"]
632 ref = [
633 SourceRef(
634 source_file_name=self.filenames[-1],
635 col_start=-1,
636 col_end=-1,
637 row_start=-1,
638 row_end=-1,
639 )
640 ]
641 return [
642 CASTLiteralValue(
643 StructureType.LIST,
644 "NotImplemented",
645 source_code_data_type,
646 ref,
647 )
648 ]
650 @visit.register
651 def visit_Assign(
652 self,
653 node: ast.Assign,
654 prev_scope_id_dict,
655 curr_scope_id_dict,
656 ):
657 """Visits a PyAST Assign node, and returns its CAST representation.
658 Either the assignment is simple, like x = {expression},
659 or the assignment is complex, like x = y = z = ... {expression}
660 Which determines how we generate the CAST for this node.
662 Args:
663 node (ast.Assign): A PyAST Assignment node.
665 Returns:
666 Assignment: An assignment CAST node
667 """
669 ref = [
670 SourceRef(
671 source_file_name=self.filenames[-1],
672 col_start=node.col_offset,
673 col_end=node.end_col_offset,
674 row_start=node.lineno,
675 row_end=node.end_lineno,
676 )
677 ]
679 left = []
680 right = []
682 if len(node.targets) == 1:
683 # x = 1, or maybe x = y, in general x = {expression}
684 if isinstance(node.targets[0], ast.Subscript):
685 # List subscript nodes get replaced out by
686 # A function call to a "list_set"
687 sub_node = node.targets[0]
688 if isinstance(node.value, ast.Subscript):
689 unique_name = construct_unique_name(
690 self.filenames[-1], "_get"
691 )
692 if unique_name not in prev_scope_id_dict.keys():
693 # If a built-in is called, then it gets added to the global dictionary if
694 # it hasn't been called before. This is to maintain one consistent ID per built-in
695 # function
696 if (
697 unique_name
698 not in self.global_identifier_dict.keys()
699 ):
700 self.insert_next_id(
701 self.global_identifier_dict, unique_name
702 )
704 prev_scope_id_dict[
705 unique_name
706 ] = self.global_identifier_dict[unique_name]
707 idx = self.visit(
708 node.value.slice,
709 prev_scope_id_dict,
710 curr_scope_id_dict,
711 )[0]
712 val = self.visit(
713 node.value.value,
714 prev_scope_id_dict,
715 curr_scope_id_dict,
716 )[0]
717 args = [val, idx]
719 val = Call(
720 func=Name(
721 "_get",
722 id=prev_scope_id_dict[unique_name],
723 source_refs=ref,
724 ),
725 arguments=args,
726 source_refs=ref,
727 )
728 else:
729 val = self.visit(
730 node.value, prev_scope_id_dict, curr_scope_id_dict
731 )[0]
733 idx = self.visit(
734 sub_node.slice, prev_scope_id_dict, curr_scope_id_dict
735 )[0]
736 list_name = self.visit(
737 sub_node.value, prev_scope_id_dict, curr_scope_id_dict
738 )[0]
739 # print("-------------")
740 # print(type(node.value))
741 # print(type(sub_node.slice))
742 # print(type(sub_node.value))
743 # print("-------------")
745 # In the case we're calling a function that doesn't have an identifier already
746 # This should only be the case for built-in python functions (i.e print, len, etc...)
747 # Otherwise it would be an error to call a function before it is defined
748 # (An ID would exist for a user-defined function here even if it isn't visited yet because of deferment)
749 unique_name = construct_unique_name(self.filenames[-1], "_set")
750 if unique_name not in prev_scope_id_dict.keys():
752 # If a built-in is called, then it gets added to the global dictionary if
753 # it hasn't been called before. This is to maintain one consistent ID per built-in
754 # function
755 if unique_name not in self.global_identifier_dict.keys():
756 self.insert_next_id(
757 self.global_identifier_dict, unique_name
758 )
760 prev_scope_id_dict[
761 unique_name
762 ] = self.global_identifier_dict[unique_name]
764 args = [list_name, idx, val]
765 return [
766 Assignment(
767 Var(val=list_name, type="Any", source_refs=ref),
768 Call(
769 func=Name(
770 "_set",
771 id=prev_scope_id_dict[unique_name],
772 source_refs=ref,
773 ),
774 arguments=args,
775 source_refs=ref,
776 ),
777 source_refs=ref,
778 )
779 ]
781 if isinstance(node.value, ast.Subscript):
783 # In the case we're calling a function that doesn't have an identifier already
784 # This should only be the case for built-in python functions (i.e print, len, etc...)
785 # Otherwise it would be an error to call a function before it is defined
786 # (An ID would exist for a user-defined function here even if it isn't visited yet because of deferment)
787 unique_name = construct_unique_name(self.filenames[-1], "_get")
788 if unique_name not in prev_scope_id_dict.keys():
790 # If a built-in is called, then it gets added to the global dictionary if
791 # it hasn't been called before. This is to maintain one consistent ID per built-in
792 # function
793 if unique_name not in self.global_identifier_dict.keys():
794 self.insert_next_id(
795 self.global_identifier_dict, unique_name
796 )
798 prev_scope_id_dict[
799 unique_name
800 ] = self.global_identifier_dict[unique_name]
802 var_name = self.visit(
803 node.targets[0], prev_scope_id_dict, curr_scope_id_dict
804 )[0]
805 idx = self.visit(
806 node.value.slice, prev_scope_id_dict, curr_scope_id_dict
807 )[0]
808 val = self.visit(
809 node.value.value, prev_scope_id_dict, curr_scope_id_dict
810 )[0]
811 args = [val, idx]
812 return [
813 Assignment(
814 var_name,
815 Call(
816 func=Name(
817 "_get",
818 id=prev_scope_id_dict[unique_name],
819 source_refs=ref,
820 ),
821 arguments=args,
822 source_refs=ref,
823 ),
824 source_refs=ref,
825 )
826 ]
828 if isinstance(node.value, ast.BinOp): # Checking if we have an assignment of the form
829 # x = LIST * NUM or x = NUM * LIST
830 binop = node.value
831 list_node = None
832 operand = None
833 if isinstance(binop.left, ast.List):
834 list_node = binop.left
835 operand = binop.right
836 elif isinstance(binop.right, ast.List):
837 list_node = binop.right
838 operand = binop.left
840 if list_node is not None:
841 cons = ValueConstructor()
842 lit_type = (
843 ScalarType.ABSTRACTFLOAT
844 if type(list_node.elts[0].value) == float
845 else ScalarType.INTEGER
846 )
847 cons.dim = None
848 t = get_op(binop.op)
849 cons.operator = (
850 "*"
851 if get_op(binop.op) == "Mult"
852 else "+"
853 if get_op(binop.op) == "Add"
854 else None
855 )
856 cons.size = self.visit(
857 operand, prev_scope_id_dict, curr_scope_id_dict
858 )[0]
859 cons.initial_value = CASTLiteralValue(
860 value_type=lit_type,
861 value=list_node.elts[0].value,
862 source_code_data_type=["Python", "3.8", "Float"],
863 source_refs=ref,
864 )
866 # TODO: Source code data type metadata
867 to_ret = CASTLiteralValue(
868 value_type="List[Any]",
869 value=cons,
870 source_code_data_type=["Python", "3.8", "List"],
871 source_refs=ref,
872 )
873 unique_name = construct_unique_name(
874 self.filenames[-1], "_get"
875 )
876 if unique_name not in prev_scope_id_dict.keys():
878 # If a built-in is called, then it gets added to the global dictionary if
879 # it hasn't been called before. This is to maintain one consistent ID per built-in
880 # function
881 if (
882 unique_name
883 not in self.global_identifier_dict.keys()
884 ):
885 self.insert_next_id(
886 self.global_identifier_dict, unique_name
887 )
889 prev_scope_id_dict[
890 unique_name
891 ] = self.global_identifier_dict[unique_name]
893 # TODO: Augment this _List_num constructor with the following
894 # First argument should be a list with the initial amount of elements
895 # Then second arg is how many times to repeat that
896 # When we say List for the first argument: It should be a literal value List that holds the elements
897 to_ret = Call(
898 func=Name(
899 "_List_num",
900 id=prev_scope_id_dict[unique_name],
901 source_refs=ref,
902 ),
903 arguments=[cons.initial_value, cons.size],
904 source_refs=ref,
905 )
907 # print(to_ret)
908 l_visit = self.visit(
909 node.targets[0], prev_scope_id_dict, curr_scope_id_dict
910 )
911 left.extend(l_visit)
912 return [Assignment(left[0], to_ret, source_refs=ref)]
914 l_visit = self.visit(
915 node.targets[0], prev_scope_id_dict, curr_scope_id_dict
916 )
917 r_visit = self.visit(
918 node.value, prev_scope_id_dict, curr_scope_id_dict
919 )
920 left.extend(l_visit)
921 right.extend(r_visit)
922 elif len(node.targets) > 1:
923 # x = y = z = ... {Expression} (multiple assignments in one line)
924 left.extend(
925 self.visit(
926 node.targets[0], prev_scope_id_dict, curr_scope_id_dict
927 )
928 )
929 node.targets = node.targets[1:]
930 right.extend(
931 self.visit(node, prev_scope_id_dict, curr_scope_id_dict)
932 )
933 else:
934 raise ValueError(
935 f"Unexpected number of targets for node: {len(node.targets)}"
936 )
938 # ref = [SourceRef(source_file_name=self.filenames[-1], col_start=node.col_offset, col_end=node.end_col_offset, row_start=node.lineno, row_end=node.end_lineno)]
940 return [Assignment(left[0], right[0], source_refs=ref)]
942 @visit.register
943 def visit_Attribute(
944 self,
945 node: ast.Attribute,
946 prev_scope_id_dict,
947 curr_scope_id_dict,
948 ):
949 """Visits a PyAST Attribute node, which is used when accessing
950 the attribute of a class. Whether it's a field or method of a class.
952 Args:
953 node (ast.Attribute): A PyAST Attribute node
955 Returns:
956 Attribute: A CAST Attribute node representing an Attribute access
957 """
958 # node.value and node.attr
959 # node.value is some kind of AST node
960 # node.attr is a string (or perhaps name)
962 # node.value.id gets us module name (string)
963 # node.attr gets us attribute we're accessing (string)
964 # helper(node.attr) -> "module_name".node.attr
966 # x.T -> node.value: the node x (Name) -> node.attr is just "T"
968 ref = [
969 SourceRef(
970 source_file_name=self.filenames[-1],
971 col_start=node.col_offset,
972 col_end=node.end_col_offset,
973 row_start=node.lineno,
974 row_end=node.end_lineno,
975 )
976 ]
978 value_cast = self.visit(
979 node.value, prev_scope_id_dict, curr_scope_id_dict
980 )
981 unique_name = (
982 node.attr
983 ) # TODO: This unique name might change to better reflect what it belongs to (i.e. x.T instead of just T)
985 if isinstance(node.ctx, ast.Load):
986 if unique_name not in curr_scope_id_dict:
987 if unique_name in prev_scope_id_dict:
988 curr_scope_id_dict[unique_name] = prev_scope_id_dict[
989 unique_name
990 ]
991 else:
992 if (
993 unique_name not in self.global_identifier_dict
994 ): # added for random.seed not exising, and other modules like that. in other words for functions in modules that we don't have visibility for.
995 self.insert_next_id(
996 self.global_identifier_dict, unique_name
997 )
998 curr_scope_id_dict[
999 unique_name
1000 ] = self.global_identifier_dict[unique_name]
1001 if isinstance(node.ctx, ast.Store):
1002 if unique_name not in curr_scope_id_dict:
1003 if unique_name in prev_scope_id_dict:
1004 curr_scope_id_dict[unique_name] = prev_scope_id_dict[
1005 unique_name
1006 ]
1007 else:
1008 self.insert_next_id(curr_scope_id_dict, unique_name)
1010 attr_cast = Name(
1011 name=node.attr, id=curr_scope_id_dict[unique_name], source_refs=ref
1012 )
1014 return [Attribute(value_cast[0], attr_cast, source_refs=ref)]
1016 @visit.register
1017 def visit_AugAssign(
1018 self,
1019 node: ast.AugAssign,
1020 prev_scope_id_dict,
1021 curr_scope_id_dict,
1022 ):
1023 """Visits a PyAST AugAssign node, which is used for an
1024 augmented assignment, like x += 1. AugAssign node is converted
1025 to a regular PyAST Assign node and passed to that visitor to
1026 generate CAST.
1028 Args:
1029 node (ast.AugAssign): A PyAST AugAssign node
1031 Returns:
1032 Assign: A CAST Assign node, generated by the Assign visitor.
1033 """
1035 # Convert AugAssign to regular Assign, and visit
1036 target = node.target
1037 value = node.value
1039 if isinstance(target, ast.Attribute):
1040 convert = ast.Assign(
1041 targets=[target],
1042 value=ast.BinOp(
1043 left=target,
1044 op=node.op,
1045 right=value,
1046 col_offset=node.col_offset,
1047 end_col_offset=node.end_col_offset,
1048 lineno=node.lineno,
1049 end_lineno=node.end_lineno,
1050 ),
1051 col_offset=node.col_offset,
1052 end_col_offset=node.end_col_offset,
1053 lineno=node.lineno,
1054 end_lineno=node.end_lineno,
1055 )
1056 elif isinstance(target, ast.Subscript):
1057 convert = ast.Assign(
1058 targets=[target],
1059 value=ast.BinOp(
1060 left=target,
1061 ctx=ast.Load(),
1062 op=node.op,
1063 right=value,
1064 col_offset=node.col_offset,
1065 end_col_offset=node.end_col_offset,
1066 lineno=node.lineno,
1067 end_lineno=node.end_lineno,
1068 ),
1069 col_offset=node.col_offset,
1070 end_col_offset=node.end_col_offset,
1071 lineno=node.lineno,
1072 end_lineno=node.end_lineno,
1073 )
1074 else:
1075 convert = ast.Assign(
1076 targets=[target],
1077 value=ast.BinOp(
1078 left=ast.Name(
1079 target.id,
1080 ctx=ast.Load(),
1081 col_offset=node.col_offset,
1082 end_col_offset=node.end_col_offset,
1083 lineno=node.lineno,
1084 end_lineno=node.end_lineno,
1085 ),
1086 op=node.op,
1087 right=value,
1088 col_offset=node.col_offset,
1089 end_col_offset=node.end_col_offset,
1090 lineno=node.lineno,
1091 end_lineno=node.end_lineno,
1092 ),
1093 col_offset=node.col_offset,
1094 end_col_offset=node.end_col_offset,
1095 lineno=node.lineno,
1096 end_lineno=node.end_lineno,
1097 )
1099 return self.visit(convert, prev_scope_id_dict, curr_scope_id_dict)
1101 @visit.register
1102 def visit_BinOp(
1103 self,
1104 node: ast.BinOp,
1105 prev_scope_id_dict,
1106 curr_scope_id_dict,
1107 ):
1108 """Visits a PyAST BinOp node, which consists of all the arithmetic
1109 and bitwise operators.
1111 Args:
1112 node (ast.BinOp): A PyAST Binary operator node
1114 Returns:
1115 Operator: A CAST operator node representing a math
1116 operation (arithmetic or bitwise)
1117 """
1119 left = self.visit(node.left, prev_scope_id_dict, curr_scope_id_dict)
1120 op = get_op(node.op)
1121 right = self.visit(node.right, prev_scope_id_dict, curr_scope_id_dict)
1123 ref = [
1124 SourceRef(
1125 source_file_name=self.filenames[-1],
1126 col_start=node.col_offset,
1127 col_end=node.end_col_offset,
1128 row_start=node.lineno,
1129 row_end=node.end_lineno,
1130 )
1131 ]
1132 leftb = []
1133 rightb = []
1135 if len(left) > 1:
1136 leftb = left[0:-1]
1137 if len(right) > 1:
1138 rightb = right[0:-1]
1140 return (
1141 leftb
1142 + rightb
1143 + [Operator(source_language="Python",
1144 interpreter="Python",
1145 version=get_python_version(),
1146 op=op,
1147 operands=[left[-1], right[-1]],
1148 source_refs=ref)]
1149 )
1151 @visit.register
1152 def visit_Break(
1153 self,
1154 node: ast.Break,
1155 prev_scope_id_dict,
1156 curr_scope_id_dict,
1157 ):
1158 """Visits a PyAST Break node, which is just a break statement
1159 nothing to be done for a Break node, just return a ModelBreak()
1160 object
1162 Args:
1163 node (ast.Break): An AST Break node
1165 Returns:
1166 ModelBreak: A CAST Break node
1168 """
1170 ref = [
1171 SourceRef(
1172 source_file_name=self.filenames[-1],
1173 col_start=node.col_offset,
1174 col_end=node.end_col_offset,
1175 row_start=node.lineno,
1176 row_end=node.end_lineno,
1177 )
1178 ]
1179 return [ModelBreak(source_refs=ref)]
1181 def create_binary_compare_tree(self, node):
1182 if isinstance(node, (ast.Compare, ast.UnaryOp, ast.Call, ast.Name, ast.Attribute, ast.Constant, ast.Subscript)):
1183 return node
1184 # elif isinstance(node, ast.UnaryOp):
1185 # return node
1186 #elif isinstance(node, ast.Call):
1187 # return node
1188 elif isinstance(node, ast.BoolOp):
1189 # > 2 values implies nested 'and' or 'or'
1190 if len(node.values) > 2:
1191 op = [node.op]
1192 # left_most = self.create_binary_compare_tree(node.values[0])
1193 # Build binary trees of ast.Compare nodes their children being
1194 # the original leaves
1195 # In other words decompress the tree so it's binary instead of n-ary
1197 idx = len(node.values) - 1
1198 tree_root = None
1199 while idx >= 0:
1200 if tree_root == None:
1201 left_child = self.create_binary_compare_tree(node.values[idx - 1])
1202 right_child = self.create_binary_compare_tree(node.values[idx])
1204 compare_op = ast.Compare(
1205 left=left_child,
1206 ops=op,
1207 comparators=[right_child],
1208 col_offset=node.col_offset,
1209 end_col_offset=node.end_col_offset,
1210 lineno=node.lineno,
1211 end_lineno=node.end_lineno,
1212 )
1214 tree_root = compare_op
1215 idx = idx - 2
1216 else:
1217 left_child = self.create_binary_compare_tree(node.values[idx])
1219 compare_op = ast.Compare(
1220 left=left_child,
1221 ops=op,
1222 comparators=[tree_root],
1223 col_offset=node.col_offset,
1224 end_col_offset=node.end_col_offset,
1225 lineno=node.lineno,
1226 end_lineno=node.end_lineno,
1227 )
1229 tree_root = compare_op
1230 idx = idx - 1
1232 return tree_root
1233 else:
1234 op = [node.op]
1236 left_child = self.create_binary_compare_tree(node.values[0])
1237 right_child = self.create_binary_compare_tree(node.values[1])
1238 compare_op = ast.Compare(
1239 left=left_child,
1240 ops=op,
1241 comparators=[right_child],
1242 col_offset=node.col_offset,
1243 end_col_offset=node.end_col_offset,
1244 lineno=node.lineno,
1245 end_lineno=node.end_lineno,
1246 )
1248 return compare_op
1249 print(f"catch type {type(node)}")
1251 @visit.register
1252 def visit_BoolOp(
1253 self,
1254 node: ast.BoolOp,
1255 prev_scope_id_dict,
1256 curr_scope_id_dict,
1257 ):
1258 """Visits a BoolOp node, which is a boolean operation connected with 'and'/'or's
1259 The BoolOp node gets converted into an AST Compare node, and then the work is
1260 passed off to it.
1262 Args:
1263 node (ast.BoolOp): An AST BoolOp node
1265 Returns:
1266 BinaryOp: A BinaryOp node that is composed of operations connected with 'and'/'or's
1268 """
1270 x = self.create_binary_compare_tree(node)
1271 """
1272 print("Root")
1273 print(x.ops)
1274 print(x.left)
1275 print(x.comparators)
1277 print("Left")
1278 print(x.left.ops)
1279 print(x.left.left)
1280 print(x.left.comparators)
1282 print("Right")
1283 print(x.comparators[0].ops)
1284 print(x.comparators[0].left)
1285 print(x.comparators[0].comparators)
1286 """
1288 return self.visit(x, prev_scope_id_dict, curr_scope_id_dict)
1290 @visit.register
1291 def visit_Call(
1292 self,
1293 node: ast.Call,
1294 prev_scope_id_dict,
1295 curr_scope_id_dict,
1296 ):
1297 """Visits a PyAST Call node, which represents a function call.
1298 Special care must be taken to see if it's a function call or a class's
1299 method call. The CAST is generated a little different depending on
1300 what kind of call it is.
1302 Args:
1303 node (ast.Call): a PyAST Call node
1305 Returns:
1306 Call: A CAST function call node
1307 """
1309 args = []
1310 func_args = []
1311 kw_args = []
1312 ref = [
1313 SourceRef(
1314 source_file_name=self.filenames[-1],
1315 col_start=node.col_offset,
1316 col_end=node.end_col_offset,
1317 row_start=node.lineno,
1318 row_end=node.end_lineno,
1319 )
1320 ]
1322 if len(node.args) > 0:
1323 for arg in node.args:
1324 if isinstance(arg, ast.Subscript):
1325 unique_name = construct_unique_name(
1326 self.filenames[-1], "_get"
1327 )
1328 if unique_name not in prev_scope_id_dict.keys():
1329 # If a built-in is called, then it gets added to the global dictionary if
1330 # it hasn't been called before. This is to maintain one consistent ID per built-in
1331 # function
1332 if (
1333 unique_name
1334 not in self.global_identifier_dict.keys()
1335 ):
1336 self.insert_next_id(
1337 self.global_identifier_dict, unique_name
1338 )
1340 prev_scope_id_dict[
1341 unique_name
1342 ] = self.global_identifier_dict[unique_name]
1343 idx = self.visit(
1344 arg.slice, prev_scope_id_dict, curr_scope_id_dict
1345 )[0]
1346 val = self.visit(
1347 arg.value, prev_scope_id_dict, curr_scope_id_dict
1348 )[0]
1349 args = [val, idx]
1351 func_args.extend(
1352 [
1353 Call(
1354 func=Name(
1355 "_get",
1356 id=prev_scope_id_dict[unique_name],
1357 source_refs=ref,
1358 ),
1359 arguments=args,
1360 source_refs=ref,
1361 )
1362 ]
1363 )
1364 elif isinstance(arg, ast.Starred):
1365 if isinstance(arg.value, ast.Subscript):
1366 func_args.append(
1367 Name(
1368 name=arg.value.value.id, id=-1, source_refs=ref
1369 )
1370 )
1371 else:
1372 func_args.append(
1373 Name(name=arg.value.id, id=-1, source_refs=ref)
1374 )
1375 else:
1376 res = self.visit(
1377 arg, prev_scope_id_dict, curr_scope_id_dict
1378 )
1379 if res != None:
1380 func_args.extend(res)
1382 # g(3,id=4) TODO: Think more about this
1383 if len(node.keywords) > 0:
1384 for arg in node.keywords:
1385 # print(prev_scope_id_dict)
1386 # print(curr_scope_id_dict)
1387 if arg.arg != None:
1388 val = self.visit(
1389 arg.value, prev_scope_id_dict, curr_scope_id_dict
1390 )[0]
1392 assign_node = Assignment(
1393 left=Var(
1394 Name(
1395 name=arg.arg, id=100, source_refs=ref
1396 ), # TODO: add a proper ID here
1397 type="float",
1398 source_refs=ref,
1399 ),
1400 right=val,
1401 source_refs=ref,
1402 )
1403 elif isinstance(arg.value, ast.Dict):
1404 val = self.visit(
1405 arg.value, prev_scope_id_dict, curr_scope_id_dict
1406 )[0]
1407 assign_node = val
1408 else:
1409 if isinstance(arg.value, ast.Attribute) and isinstance(
1410 arg.value.value, ast.Attribute
1411 ):
1412 assign_node = Name(
1413 name=arg.value.value.attr, id=-1, source_refs=ref
1414 )
1415 elif isinstance(arg.value, ast.Call):
1416 assign_node = Name(
1417 name=arg.value.func.id, id=-1, source_refs=ref
1418 )
1419 elif isinstance(arg.value, ast.Attribute):
1420 assign_node = Name(
1421 name=arg.value.value, id=-1, source_refs=ref
1422 )
1423 elif isinstance(arg.value, ast.Subscript):
1424 assign_node = Name(
1425 name=arg.value.value.id, id=-1, source_refs=ref
1426 )
1427 else:
1428 assign_node = Name(
1429 name=arg.value.id, id=-1, source_refs=ref
1430 )
1431 kw_args.append(assign_node)
1432 # kw_args.extend(self.visit(arg.value, prev_scope_id_dict, curr_scope_id_dict))
1434 args = func_args + kw_args
1436 if isinstance(node.func, ast.Attribute):
1437 # print(node.func.attr)
1438 res = self.visit(node.func, prev_scope_id_dict, curr_scope_id_dict)
1439 return [Call(func=res[0], arguments=args, source_refs=ref)]
1440 else:
1441 # In the case we're calling a function that doesn't have an identifier already
1442 # This should only be the case for built-in python functions (i.e print, len, etc...)
1443 # Otherwise it would be an error to call a function before it is defined
1444 # (An ID would exist for a user-defined function here even if it isn't visited yet because of deferment)
1445 if isinstance(node.func, ast.Call):
1446 if node.func.func.id == "list":
1447 unique_name = construct_unique_name(
1448 self.filenames[-1], "cast"
1449 )
1450 else:
1451 unique_name = construct_unique_name(
1452 self.filenames[-1], node.func.func.id
1453 )
1454 else:
1455 if node.func.id == "list":
1456 unique_name = construct_unique_name(
1457 self.filenames[-1], "cast"
1458 )
1459 else:
1460 unique_name = construct_unique_name(
1461 self.filenames[-1], node.func.id
1462 )
1463 if unique_name not in prev_scope_id_dict.keys(): # and unique_name not in curr_scope_id_dict.keys():
1464 # If a built-in is called, then it gets added to the global dictionary if
1465 # it hasn't been called before. This is to maintain one consistent ID per built-in
1466 # function
1467 if unique_name not in self.global_identifier_dict.keys():
1468 self.insert_next_id(
1469 self.global_identifier_dict, unique_name
1470 )
1472 prev_scope_id_dict[unique_name] = self.global_identifier_dict[
1473 unique_name
1474 ]
1476 if isinstance(node.func, ast.Call):
1477 if node.func.func.id == "list":
1478 args.append(
1479 CASTLiteralValue(
1480 StructureType.LIST,
1481 node.func.func.id,
1482 ["Python", "3.8", "List"],
1483 ref,
1484 )
1485 )
1486 return [
1487 Call(
1488 func=Name(
1489 "cast",
1490 id=prev_scope_id_dict[unique_name],
1491 source_refs=ref,
1492 ),
1493 arguments=args,
1494 source_refs=ref,
1495 )
1496 ]
1497 else:
1498 return [
1499 Call(
1500 func=Name(
1501 node.func.func.id,
1502 id=prev_scope_id_dict[unique_name],
1503 source_refs=ref,
1504 ),
1505 arguments=args,
1506 source_refs=ref,
1507 )
1508 ]
1509 else:
1510 if node.func.id == "list":
1511 args.append(
1512 CASTLiteralValue(
1513 StructureType.LIST,
1514 node.func.id,
1515 ["Python", "3.8", "List"],
1516 ref,
1517 )
1518 )
1519 return [
1520 Call(
1521 func=Name(
1522 "cast",
1523 id=prev_scope_id_dict[unique_name],
1524 source_refs=ref,
1525 ),
1526 arguments=args,
1527 source_refs=ref,
1528 )
1529 ]
1530 else:
1531 if node.func.id in self.curr_func_args:
1532 unique_name = construct_unique_name(
1533 self.filenames[-1], "_call"
1534 )
1535 if unique_name not in prev_scope_id_dict.keys(): # and unique_name not in curr_scope_id_dict.keys():
1536 # If a built-in is called, then it gets added to the global dictionary if
1537 # it hasn't been called before. This is to maintain one consistent ID per built-in
1538 # function
1539 if unique_name not in self.global_identifier_dict.keys():
1540 self.insert_next_id(
1541 self.global_identifier_dict, unique_name
1542 )
1544 prev_scope_id_dict[unique_name] = self.global_identifier_dict[
1545 unique_name
1546 ]
1547 unique_name = construct_unique_name(
1548 self.filenames[-1], node.func.id
1549 )
1550 if unique_name not in prev_scope_id_dict.keys(): # and unique_name not in curr_scope_id_dict.keys():
1551 # If a built-in is called, then it gets added to the global dictionary if
1552 # it hasn't been called before. This is to maintain one consistent ID per built-in
1553 # function
1554 if unique_name not in self.global_identifier_dict.keys():
1555 self.insert_next_id(
1556 self.global_identifier_dict, unique_name
1557 )
1559 prev_scope_id_dict[unique_name] = self.global_identifier_dict[
1560 unique_name
1561 ]
1562 func_name_arg = Name(name=node.func.id, id=prev_scope_id_dict[unique_name], source_refs=ref)
1564 return [
1565 Call(
1566 func=Name(
1567 "_call",
1568 id=curr_scope_id_dict[unique_name] if unique_name in curr_scope_id_dict else prev_scope_id_dict[unique_name], # NOTE: do this everywhere?
1569 source_refs=ref,
1570 ),
1571 arguments=[func_name_arg]+args,
1572 source_refs=ref,
1573 )
1574 ]
1575 else:
1576 return [
1577 Call(
1578 func=Name(
1579 node.func.id,
1580 id=curr_scope_id_dict[unique_name] if unique_name in curr_scope_id_dict else prev_scope_id_dict[unique_name], # NOTE: do this everywhere?
1581 source_refs=ref,
1582 ),
1583 arguments=args,
1584 source_refs=ref,
1585 )
1586 ]
1588 def collect_fields(
1589 self, node: ast.FunctionDef, prev_scope_id_dict, curr_scope_id_dict
1590 ):
1591 """Attempts to solve the problem of collecting any additional fields
1592 for a class that get created in functions outside of __init__
1593 """
1594 fields = []
1595 for n in node.body:
1596 if isinstance(n, ast.Assign) and isinstance(
1597 n.targets[0], ast.Attribute
1598 ):
1599 attr_node = n.targets[0]
1600 if isinstance(attr_node.value, ast.Attribute):
1601 if attr_node.value.value == "self":
1602 ref = [
1603 SourceRef(
1604 source_file_name=self.filenames[-1],
1605 col_start=attr_node.col_offset,
1606 col_end=attr_node.end_col_offset,
1607 row_start=attr_node.lineno,
1608 row_end=attr_node.end_lineno,
1609 )
1610 ]
1611 # Need IDs for name, which one?
1612 attr_id = self.insert_next_id(
1613 curr_scope_id_dict, attr_node.value.attr
1614 )
1615 fields.append(
1616 Var(
1617 Name(
1618 attr_node.value.attr,
1619 id=attr_id,
1620 source_refs=ref,
1621 ),
1622 "float",
1623 source_refs=ref,
1624 )
1625 )
1626 elif attr_node.value.id == "self":
1627 ref = [
1628 SourceRef(
1629 source_file_name=self.filenames[-1],
1630 col_start=attr_node.col_offset,
1631 col_end=attr_node.end_col_offset,
1632 row_start=attr_node.lineno,
1633 row_end=attr_node.end_lineno,
1634 )
1635 ]
1636 # Need IDs for name, which one?
1637 attr_id = self.insert_next_id(
1638 curr_scope_id_dict, attr_node.attr
1639 )
1640 fields.append(
1641 Var(
1642 Name(attr_node.attr, id=attr_id, source_refs=ref),
1643 "float",
1644 source_refs=ref,
1645 )
1646 )
1648 return fields
1650 @visit.register
1651 def visit_ClassDef(
1652 self,
1653 node: ast.ClassDef,
1654 prev_scope_id_dict,
1655 curr_scope_id_dict,
1656 ):
1657 """Visits a PyAST ClassDef node, which is used to define user classes.
1658 Acquiring the fields of the class involves going through the __init__
1659 function and seeing if the attributes are associated with the self
1660 parameter. In addition, we add to the 'classes' dictionary the name of
1661 the class and a list of all its functions.
1663 Args:
1664 node (ast.ClassDef): A PyAST class definition node
1666 Returns:
1667 ClassDef: A CAST class definition node
1668 """
1669 name = node.name
1670 self.classes[name] = []
1672 bases = []
1673 for base in node.bases:
1674 bases.extend(
1675 self.visit(base, prev_scope_id_dict, curr_scope_id_dict)
1676 )
1678 fields = []
1679 funcs = []
1680 for func in node.body:
1681 if isinstance(func, ast.FunctionDef):
1682 if func.name != "__init__":
1683 fields.extend(
1684 self.collect_fields(
1685 func, prev_scope_id_dict, curr_scope_id_dict
1686 )
1687 )
1688 funcs.extend(
1689 self.visit(func, prev_scope_id_dict, curr_scope_id_dict)
1690 )
1691 # curr_scope_id_dict = {}
1692 # if isinstance(func,ast.FunctionDef):
1693 self.classes[name].append(func.name)
1694 # self.insert_next_id(prev_scope_id_dict, name)
1695 self.insert_next_id(prev_scope_id_dict, f"{name}.{func.name}")
1697 # print(prev_scope_id_dict)
1698 # print(curr_scope_id_dict)
1699 # Get the fields in the class from init
1700 init_func = None
1701 for f in node.body:
1702 if isinstance(f, ast.FunctionDef) and f.name == "__init__":
1703 init_func = f.body
1704 break
1706 if init_func != None:
1707 for func_node in init_func:
1708 if isinstance(func_node, ast.Assign) and isinstance(
1709 func_node.targets[0], ast.Attribute
1710 ):
1711 attr_node = func_node.targets[0]
1712 if attr_node.value.id == "self":
1713 ref = [
1714 SourceRef(
1715 source_file_name=self.filenames[-1],
1716 col_start=attr_node.col_offset,
1717 col_end=attr_node.end_col_offset,
1718 row_start=attr_node.lineno,
1719 row_end=attr_node.end_lineno,
1720 )
1721 ]
1722 # Need IDs for name, which one?
1723 attr_id = self.insert_next_id(
1724 curr_scope_id_dict, attr_node.attr
1725 )
1726 fields.append(
1727 Var(
1728 Name(
1729 attr_node.attr, id=attr_id, source_refs=ref
1730 ),
1731 "float",
1732 source_refs=ref,
1733 )
1734 )
1736 ref = [
1737 SourceRef(
1738 source_file_name=self.filenames[-1],
1739 col_start=node.col_offset,
1740 col_end=node.end_col_offset,
1741 row_start=node.lineno,
1742 row_end=node.end_lineno,
1743 )
1744 ]
1745 return [RecordDef(name, bases, funcs, fields, source_refs=ref)]
1747 @visit.register
1748 def visit_Compare(
1749 self,
1750 node: ast.Compare,
1751 prev_scope_id_dict,
1752 curr_scope_id_dict,
1753 ):
1754 """Visits a PyAST Compare node, which consists of boolean operations
1756 Args:
1757 node (ast.Compare): A PyAST Compare node
1759 Returns:
1760 Operator: An Operator node, which in this case will hold a boolean
1761 operation
1762 """
1763 ref = [
1764 SourceRef(
1765 source_file_name=self.filenames[-1],
1766 col_start=node.col_offset,
1767 col_end=node.end_col_offset,
1768 row_start=node.lineno,
1769 row_end=node.end_lineno,
1770 )
1771 ]
1774 # Fetch the first element (which is in node.left)
1775 #left = node.left
1776 #ops = [op for op in node.ops]
1777 # Grab the first comparison operation
1778 # print(left)
1779 # print(node.ops)
1780 # op = get_op(node.ops.pop(0))
1781 # op = get_op(ops.pop())
1784 # maintain a stack of if statements that we build up
1785 if_stack = []
1786 source_code_data_type = ["Python", "3.8", str(type(True))]
1787 true_val = CASTLiteralValue(
1788 ScalarType.BOOLEAN,
1789 "True",
1790 source_code_data_type=source_code_data_type,
1791 source_refs=ref,
1792 )
1793 false_val = CASTLiteralValue(
1794 ScalarType.BOOLEAN,
1795 "False",
1796 source_code_data_type=source_code_data_type,
1797 source_refs=ref,
1798 )
1801 idx = len(node.comparators) - 1
1802 op_idx = len(node.ops) - 1
1803 tree_root = None
1804 while idx > 0:
1805 op = get_op(node.ops[op_idx])
1806 l = self.visit(node.comparators[idx - 1], prev_scope_id_dict, curr_scope_id_dict)[0]
1807 r = self.visit(node.comparators[idx], prev_scope_id_dict, curr_scope_id_dict)[0]
1809 if op == "ast.And":
1810 test = ModelIf(expr=l, body=[r], orelse=[false_val], source_refs=ref)
1811 elif op == "ast.Or":
1812 test = ModelIf(expr=l, body=[true_val], orelse=[r], source_refs=ref)
1813 else:
1814 test = Operator(source_language="Python", interpreter="Python",
1815 version=get_python_version(),
1816 op=op,
1817 operands=[l,r],
1818 source_refs=ref)
1819 if tree_root == None:
1820 tree_root = test
1821 else:
1822 tree_root = ModelIf(expr=test,body=[tree_root],orelse=[false_val], source_refs=ref)
1824 op_idx = op_idx - 1
1825 idx = idx - 1
1827 op = get_op(node.ops[op_idx])
1828 l = self.visit(node.left, prev_scope_id_dict, curr_scope_id_dict)[0]
1829 r = self.visit(node.comparators[idx], prev_scope_id_dict, curr_scope_id_dict)[0]
1830 if op == "ast.And":
1831 test = ModelIf(expr=l, body=[r], orelse=[false_val], source_refs=ref)
1832 elif op == "ast.Or":
1833 test = ModelIf(expr=l, body=[true_val], orelse=[r], source_refs=ref)
1834 else:
1835 test = Operator(source_language="Python", interpreter="Python",
1836 version=get_python_version(),
1837 op=op,
1838 operands=[l,r],
1839 source_refs=ref)
1841 if tree_root == None:
1842 tree_root = test
1843 else:
1844 tree_root = ModelIf(expr=test,body=[tree_root],orelse=[false_val], source_refs=ref)
1846 return [tree_root]
1849 @visit.register
1850 def visit_Constant(
1851 self,
1852 node: ast.Constant,
1853 prev_scope_id_dict,
1854 curr_scope_id_dict,
1855 ):
1856 """Visits a PyAST Constant node, which can hold either numeric or
1857 string values. A dictionary is used to index into which operation
1858 we're doing.
1860 Args:
1861 node (ast.Constant): A PyAST Constant node
1863 Returns:
1864 Number: A CAST numeric node, if the node's value is an int or float
1865 String: A CAST string node, if the node's value is a string
1866 Boolean: A CAST boolean node, if the node's value is a boolean
1868 Raises:
1869 TypeError: If the node's value is something else that isn't
1870 recognized by the other two cases
1871 """
1873 ref = [
1874 SourceRef(
1875 source_file_name=self.filenames[-1],
1876 col_start=node.col_offset,
1877 col_end=node.end_col_offset,
1878 row_start=node.lineno,
1879 row_end=node.end_lineno,
1880 )
1881 ]
1882 source_code_data_type = ["Python", "3.8", str(type(node.value))]
1883 # NOTE: We have to check the types such that no ancestor is checked before a descendant
1884 # boolean values are also seen as integers with isinstance()
1885 # TODO: Consider using type() with a map instead of isinstance to check types
1886 if isinstance(node.value, bool):
1887 return [
1888 CASTLiteralValue(
1889 ScalarType.BOOLEAN,
1890 str(node.value),
1891 source_code_data_type,
1892 ref,
1893 )
1894 ]
1895 elif isinstance(node.value, int):
1896 return [
1897 CASTLiteralValue(
1898 ScalarType.INTEGER, node.value, source_code_data_type, ref
1899 )
1900 ]
1901 elif isinstance(node.value, float):
1902 return [
1903 CASTLiteralValue(
1904 ScalarType.ABSTRACTFLOAT,
1905 node.value,
1906 source_code_data_type,
1907 ref,
1908 )
1909 ]
1910 elif isinstance(node.value, str):
1911 return [
1912 CASTLiteralValue(
1913 StructureType.LIST, node.value, source_code_data_type, ref
1914 )
1915 ]
1916 elif node.value is None:
1917 return [CASTLiteralValue(None, None, source_code_data_type, ref)]
1918 elif isinstance(node.value, type(...)):
1919 return [CASTLiteralValue(ScalarType.ELLIPSIS, "...", source_code_data_type, ref)]
1920 else:
1921 raise TypeError(f"Type {str(type(node.value))} not supported")
1923 @visit.register
1924 def visit_Continue(
1925 self,
1926 node: ast.Continue,
1927 prev_scope_id_dict,
1928 curr_scope_id_dict,
1929 ):
1930 """Visits a PyAST Continue node, which is just a continue statement
1931 nothing to be done for a Continue node, just return a ModelContinue node
1933 Args:
1934 node (ast.Continue): An AST Continue node
1936 Returns:
1937 ModelContinue: A CAST Continue node
1938 """
1940 ref = [
1941 SourceRef(
1942 source_file_name=self.filenames[-1],
1943 col_start=node.col_offset,
1944 col_end=node.end_col_offset,
1945 row_start=node.lineno,
1946 row_end=node.end_lineno,
1947 )
1948 ]
1949 return [ModelContinue(source_refs=ref)]
1951 @visit.register
1952 def visit_Dict(
1953 self,
1954 node: ast.Dict,
1955 prev_scope_id_dict,
1956 curr_scope_id_dict,
1957 ):
1958 """Visits a PyAST Dict node, which represents a dictionary.
1960 Args:
1961 node (ast.Dict): A PyAST dictionary node
1963 Returns:
1964 Dict: A CAST Dictionary node.
1965 """
1966 # TODO: when a ** shows up in a dictionary
1968 keys = []
1969 values = []
1970 if len(node.keys) > 0:
1971 for piece in node.keys:
1972 if piece != None:
1973 keys.extend(
1974 self.visit(
1975 piece, prev_scope_id_dict, curr_scope_id_dict
1976 )
1977 )
1979 if len(node.values) > 0:
1980 for piece in node.values:
1981 if piece != None:
1982 values.extend(
1983 self.visit(
1984 piece, prev_scope_id_dict, curr_scope_id_dict
1985 )
1986 )
1988 k = [e.value if hasattr(e, "value") else e for e in keys]
1989 v = [e.value if hasattr(e, "value") else e for e in values]
1991 ref = [
1992 SourceRef(
1993 source_file_name=self.filenames[-1],
1994 col_start=node.col_offset,
1995 col_end=node.end_col_offset,
1996 row_start=node.lineno,
1997 row_end=node.end_lineno,
1998 )
1999 ]
2000 for key in k:
2001 if isinstance(key, CASTLiteralValue) and key.value_type == StructureType.TUPLE:
2002 return [
2003 CASTLiteralValue(
2004 StructureType.MAP,
2005 "",
2006 source_code_data_type=["Python", "3.8", str(dict)],
2007 source_refs=ref,
2008 )
2009 ]
2011 # return [CASTLiteralValue(StructureType.MAP, str(dict(list(zip(k,v)))), source_code_data_type=["Python","3.8",str(dict)], source_refs=ref)]
2012 return [
2013 CASTLiteralValue(
2014 StructureType.MAP,
2015 str(list(zip(k, v))),
2016 source_code_data_type=["Python", "3.8", str(dict)],
2017 source_refs=ref,
2018 )
2019 ]
2021 @visit.register
2022 def visit_Expr(
2023 self,
2024 node: ast.Expr,
2025 prev_scope_id_dict,
2026 curr_scope_id_dict,
2027 ):
2028 """Visits a PyAST Expr node, which represents some kind of standalone
2029 expression.
2031 Args:
2032 node (ast.Expr): A PyAST Expression node
2034 Returns:
2035 Expr: A CAST Expression node
2036 [AstNode]: A list of AstNodes if the expression consists
2037 of more than one node
2038 """
2040 ref = [
2041 SourceRef(
2042 source_file_name=self.filenames[-1],
2043 col_start=node.col_offset,
2044 col_end=node.end_col_offset,
2045 row_start=node.lineno,
2046 row_end=node.end_lineno,
2047 )
2048 ]
2049 return self.visit(node.value, prev_scope_id_dict, curr_scope_id_dict)
2051 @visit.register
2052 def visit_For(
2053 self, node: ast.For, prev_scope_id_dict, curr_scope_id_dict
2054 ):
2055 """Visits a PyAST For node, which represents Python for loops.
2056 A For loop needs different handling than a while loop.
2057 In particular, a For loop acts on an iterator as opposed to acting on
2058 some kind of condition. In order to make this translation a little easier to handle
2059 we leverage the iterator constructs to convert the For loop into a while loop using
2060 the iterators.
2062 Args:
2063 node (ast.For): A PyAST For loop node.
2065 Returns:
2066 Loop: A CAST loop node, which generically represents both For
2067 loops and While loops.
2068 """
2070 ref = [
2071 SourceRef(
2072 source_file_name=self.filenames[-1],
2073 col_start=node.col_offset,
2074 col_end=node.end_col_offset,
2075 row_start=node.lineno,
2076 row_end=node.end_lineno,
2077 )
2078 ]
2080 target = self.visit(
2081 node.target, prev_scope_id_dict, curr_scope_id_dict
2082 )[0]
2083 iterable = self.visit(
2084 node.iter, prev_scope_id_dict, curr_scope_id_dict
2085 )[0]
2087 # The body of a loop contains its own scope (it can create variables only it can see and can use
2088 # variables from its enclosing scope) so we copy the current scope and merge scopes
2089 # to create the enclosing scope for the loop body
2090 curr_scope_copy = copy.deepcopy(curr_scope_id_dict)
2091 merge_dicts(prev_scope_id_dict, curr_scope_id_dict)
2092 loop_scope_id_dict = {}
2094 # When we pass in scopes, we pass what's currently in the previous scope along with
2095 # the curr scope which would consist of the loop variables (node.target) and the item
2096 # we loop over (iter) though the second one shouldn't ever be accessed
2097 body = []
2098 for piece in node.body + node.orelse:
2099 body.extend(
2100 self.visit(piece, curr_scope_id_dict, loop_scope_id_dict)
2101 )
2103 # Once we're out of the loop body we can copy the current scope back
2104 curr_scope_id_dict = copy.deepcopy(curr_scope_copy)
2106 # TODO: Mark these as variables that were generated by this script at some point
2107 # (^ This was a really old request, not sure if it's still needed at this point)
2108 iterator_name = f"generated_iter_{self.var_count}"
2109 self.var_count += 1
2111 iterator_id = self.insert_next_id(curr_scope_id_dict, iterator_name)
2113 # 'iter' and 'next' are python built-ins
2114 iter_id = -1
2115 if "iter" not in self.global_identifier_dict.keys():
2116 iter_id = self.insert_next_id(self.global_identifier_dict, "iter")
2117 else:
2118 iter_id = self.global_identifier_dict["iter"]
2120 if "next" not in self.global_identifier_dict.keys():
2121 next_id = self.insert_next_id(self.global_identifier_dict, "next")
2122 else:
2123 next_id = self.global_identifier_dict["next"]
2125 stop_cond_name = f"sc_{self.var_count}"
2126 self.var_count += 1
2128 stop_cond_id = self.insert_next_id(curr_scope_id_dict, stop_cond_name)
2130 iter_var_cast = Var(
2131 Name(name=iterator_name, id=iterator_id, source_refs=ref),
2132 "iterator",
2133 source_refs=ref,
2134 )
2136 stop_cond_var_cast = Var(
2137 Name(name=stop_cond_name, id=stop_cond_id, source_refs=ref),
2138 "boolean",
2139 source_refs=ref,
2140 )
2142 iter_var = Assignment(
2143 iter_var_cast,
2144 Call(
2145 func=Name(name="iter", id=iter_id, source_refs=ref),
2146 arguments=[iterable],
2147 source_refs=ref,
2148 ),
2149 source_refs=ref,
2150 )
2152 source_code_data_type = ["Python","3.8","Tuple"]
2153 first_next = Assignment(
2154 CASTLiteralValue(StructureType.TUPLE, [target, iter_var_cast, stop_cond_var_cast], source_code_data_type, source_refs=ref),
2155 Call(
2156 func=Name(name="next", id=next_id, source_refs=ref),
2157 arguments=[
2158 Var(
2159 Name(
2160 name=iterator_name, id=iterator_id, source_refs=ref
2161 ),
2162 "iterator",
2163 source_refs=ref,
2164 )
2165 ],
2166 source_refs=ref,
2167 ),
2168 source_refs=ref,
2169 )
2170 loop_cond = Operator(source_language="Python",
2171 interpreter="Python",
2172 version=get_python_version(),
2173 op="ast.Eq",
2174 operands=[stop_cond_var_cast,
2175 CASTLiteralValue(
2176 ScalarType.BOOLEAN,
2177 False,
2178 ["Python", "3.8", "boolean"],
2179 source_refs=ref,
2180 )],
2181 source_refs=ref)
2183 source_code_data_type = ["Python","3.8","Tuple"]
2184 loop_assign = Assignment(
2185 CASTLiteralValue(StructureType.TUPLE, [target, iter_var_cast, stop_cond_var_cast], source_code_data_type, source_refs=ref),
2186 Call(
2187 func=Name(name="next", id=next_id, source_refs=ref),
2188 arguments=[
2189 Var(
2190 Name(
2191 name=iterator_name, id=iterator_id, source_refs=ref
2192 ),
2193 "iterator",
2194 source_refs=ref,
2195 )
2196 ],
2197 source_refs=ref,
2198 ),
2199 source_refs=ref,
2200 )
2202 return [
2203 Loop(
2204 pre=[iter_var, first_next],
2205 expr=loop_cond,
2206 body=body + [loop_assign],
2207 post=[],
2208 source_refs=ref,
2209 )
2210 ]
2212 @visit.register
2213 def visit_FunctionDef(
2214 self,
2215 node: ast.FunctionDef,
2216 prev_scope_id_dict,
2217 curr_scope_id_dict,
2218 ):
2219 """Visits a PyAST FunctionDef node. Which is used for a Python
2220 function definition.
2222 Args:
2223 node (ast.FunctionDef): A PyAST function definition node
2225 Returns:
2226 FunctionDef: A CAST Function Definition node
2227 """
2229 # Copy the enclosing scope dictionary as it is before we visit the current function
2230 # The idea for this is to prevent any weird overwritting issues that may arise from modifying
2231 # dictionaries in place
2232 prev_scope_id_dict_copy = copy.deepcopy(prev_scope_id_dict)
2235 # Need to maintain the previous scope, so copy them over here
2236 prev_func_args = copy.deepcopy(self.curr_func_args)
2237 self.curr_func_args = []
2239 body = []
2240 args = []
2241 curr_scope_id_dict = {}
2242 arg_count = len(node.args.args)
2243 kwonlyargs_count = len(node.args.kwonlyargs)
2244 default_val_count = len(node.args.defaults)
2245 if arg_count > 0:
2246 # No argument has a default value
2247 if default_val_count == 0:
2248 for arg in node.args.args:
2249 # unique_name = construct_unique_name(self.filenames[-1], arg.arg)
2250 # self.insert_next_id(curr_scope_id_dict, arg.arg)
2251 # self.insert_next_id(curr_scope_id_dict, f"{node.name}.{arg.arg}")
2252 self.insert_next_id(curr_scope_id_dict, f"{arg.arg}")
2253 # self.insert_next_id(curr_scope_id_dict, unique_name)
2254 arg_ref = SourceRef(self.filenames[-1], arg.col_offset, arg.end_col_offset, arg.lineno, arg.end_lineno)
2255 self.curr_func_args.append(arg.arg)
2256 args.append(
2257 Var(
2258 Name(
2259 arg.arg,
2260 id=curr_scope_id_dict[arg.arg],
2261 # id=curr_scope_id_dict[f"{node.name}.{arg.arg}"],
2262 source_refs=[arg_ref]
2263 ),
2264 "float", # TODO: Correct typing instead of just 'float'
2265 None,
2266 source_refs=[arg_ref]
2267 )
2268 )
2269 else:
2270 # Implies that all arguments have default values
2271 if arg_count == default_val_count:
2272 for i, arg in enumerate(node.args.args, 0):
2273 self.insert_next_id(curr_scope_id_dict, arg.arg)
2274 val = self.visit(
2275 node.args.defaults[i],
2276 prev_scope_id_dict,
2277 curr_scope_id_dict,
2278 )[0]
2279 self.curr_func_args.append(arg.arg)
2280 args.append(
2281 Var(
2282 Name(
2283 arg.arg,
2284 id=curr_scope_id_dict[arg.arg],
2285 source_refs=[
2286 SourceRef(
2287 self.filenames[-1],
2288 arg.col_offset,
2289 arg.end_col_offset,
2290 arg.lineno,
2291 arg.end_lineno,
2292 )
2293 ],
2294 ),
2295 "float", # TODO: Correct typing instead of just 'float'
2296 val,
2297 source_refs=[
2298 SourceRef(
2299 self.filenames[-1],
2300 arg.col_offset,
2301 arg.end_col_offset,
2302 arg.lineno,
2303 arg.end_lineno,
2304 )
2305 ],
2306 )
2307 )
2309 # There's less default values than actual args, the positional-only arguments come first
2310 else:
2311 pos_idx = 0
2312 for arg in node.args.args:
2313 if arg_count == default_val_count:
2314 break
2315 self.insert_next_id(curr_scope_id_dict, arg.arg)
2316 self.curr_func_args.append(arg.arg)
2317 args.append(
2318 Var(
2319 Name(
2320 arg.arg,
2321 id=curr_scope_id_dict[arg.arg],
2322 source_refs=[
2323 SourceRef(
2324 self.filenames[-1],
2325 arg.col_offset,
2326 arg.end_col_offset,
2327 arg.lineno,
2328 arg.end_lineno,
2329 )
2330 ],
2331 ),
2332 "float", # TODO: Correct typing instead of just 'float'
2333 None,
2334 source_refs=[
2335 SourceRef(
2336 self.filenames[-1],
2337 arg.col_offset,
2338 arg.end_col_offset,
2339 arg.lineno,
2340 arg.end_lineno,
2341 )
2342 ],
2343 )
2344 )
2346 pos_idx += 1
2347 arg_count -= 1
2349 default_index = 0
2350 while arg_count > 0:
2351 # unique_name = construct_unique_name(self.filenames[-1], arg.arg)
2352 arg = node.args.args[pos_idx]
2353 self.insert_next_id(curr_scope_id_dict, arg.arg)
2354 val = self.visit(
2355 node.args.defaults[default_index],
2356 prev_scope_id_dict,
2357 curr_scope_id_dict,
2358 )[0]
2359 # self.insert_next_id(curr_scope_id_dict, unique_name)
2360 self.curr_func_args.append(arg.arg)
2361 args.append(
2362 Var(
2363 Name(
2364 arg.arg,
2365 id=curr_scope_id_dict[arg.arg],
2366 source_refs=[
2367 SourceRef(
2368 self.filenames[-1],
2369 arg.col_offset,
2370 arg.end_col_offset,
2371 arg.lineno,
2372 arg.end_lineno,
2373 )
2374 ],
2375 ),
2376 "float", # TODO: Correct typing instead of just 'float'
2377 val,
2378 source_refs=[
2379 SourceRef(
2380 self.filenames[-1],
2381 arg.col_offset,
2382 arg.end_col_offset,
2383 arg.lineno,
2384 arg.end_lineno,
2385 )
2386 ],
2387 )
2388 )
2390 pos_idx += 1
2391 arg_count -= 1
2392 default_index += 1
2394 if kwonlyargs_count > 0:
2395 for arg in node.args.kwonlyargs:
2396 # unique_name = construct_unique_name(self.filenames[-1], arg.arg)
2397 self.insert_next_id(curr_scope_id_dict, arg.arg)
2398 # self.insert_next_id(curr_scope_id_dict, unique_name)
2399 self.curr_func_args.append(arg.arg)
2400 args.append(
2401 Var(
2402 Name(
2403 arg.arg,
2404 id=curr_scope_id_dict[arg.arg],
2405 source_refs=[
2406 SourceRef(
2407 self.filenames[-1],
2408 arg.col_offset,
2409 arg.end_col_offset,
2410 arg.lineno,
2411 arg.end_lineno,
2412 )
2413 ],
2414 ),
2415 "float", # TODO: Correct typing instead of just 'float'
2416 None,
2417 source_refs=[
2418 SourceRef(
2419 self.filenames[-1],
2420 arg.col_offset,
2421 arg.end_col_offset,
2422 arg.lineno,
2423 arg.end_lineno,
2424 )
2425 ],
2426 )
2427 )
2429 # Store '*args' as a name
2430 arg = node.args.vararg
2431 if arg != None:
2432 self.insert_next_id(curr_scope_id_dict, arg.arg)
2433 args.append(
2434 Var(
2435 Name(
2436 arg.arg,
2437 id=curr_scope_id_dict[arg.arg],
2438 source_refs=[
2439 SourceRef(
2440 self.filenames[-1],
2441 arg.col_offset,
2442 arg.end_col_offset,
2443 arg.lineno,
2444 arg.end_lineno,
2445 )
2446 ],
2447 ),
2448 "float", # TODO: Correct typing instead of just 'float'
2449 None,
2450 source_refs=[
2451 SourceRef(
2452 self.filenames[-1],
2453 arg.col_offset,
2454 arg.end_col_offset,
2455 arg.lineno,
2456 arg.end_lineno,
2457 )
2458 ],
2459 )
2460 )
2462 # Store '**kwargs' as a name
2463 arg = node.args.kwarg
2464 if arg != None:
2465 self.insert_next_id(curr_scope_id_dict, arg.arg)
2466 args.append(
2467 Var(
2468 Name(
2469 arg.arg,
2470 id=curr_scope_id_dict[arg.arg],
2471 source_refs=[
2472 SourceRef(
2473 self.filenames[-1],
2474 arg.col_offset,
2475 arg.end_col_offset,
2476 arg.lineno,
2477 arg.end_lineno,
2478 )
2479 ],
2480 ),
2481 "float", # TODO: Correct typing instead of just 'float'
2482 None,
2483 source_refs=[
2484 SourceRef(
2485 self.filenames[-1],
2486 arg.col_offset,
2487 arg.end_col_offset,
2488 arg.lineno,
2489 arg.end_lineno,
2490 )
2491 ],
2492 )
2493 )
2495 functions_to_visit = []
2497 if len(node.body) > 0:
2498 # To account for nested loops we check to see if the CAST node is in a list and
2499 # extend accordingly
2501 for piece in node.body:
2502 if isinstance(piece, ast.Assign):
2503 names = get_node_name(piece)
2505 for var_name in names:
2507 # If something is overwritten in the curr_func_args then we
2508 # remove it here, as it's no longer a function
2509 if var_name in self.curr_func_args:
2510 self.curr_func_args.remove(var_name)
2512 # unique_name = construct_unique_name(
2513 # self.filenames[-1], var_name
2514 # )
2515 self.insert_next_id(curr_scope_id_dict, var_name)
2517 merge_dicts(curr_scope_id_dict, prev_scope_id_dict)
2518 # merge_dicts(prev_scope_id_dict, curr_scope_id_dict)
2519 for piece in node.body:
2521 if isinstance(piece, ast.FunctionDef):
2522 self.curr_func_args.append(piece.name)
2523 unique_name = construct_unique_name(self.filenames[-1], piece.name)
2524 self.insert_next_id(curr_scope_id_dict, unique_name)
2525 prev_scope_id_dict[unique_name] = curr_scope_id_dict[unique_name]
2526 # functions_to_visit.append(piece)
2527 #continue
2529 # print(curr_scope_id_dict)
2530 # print(prev_scope_id_dict)
2532 # Have to figure out name IDs for imports (i.e. other modules)
2533 # These asserts will keep us from visiting them from now
2534 # assert not isinstance(piece, ast.Import)
2535 # assert not isinstance(piece, ast.ImportFrom)
2537 # Check if the current node is a string literal
2538 # which means that it's a docstring and we don't need it
2539 if not check_expr_str(piece):
2540 to_add = self.visit(
2541 piece, prev_scope_id_dict, curr_scope_id_dict
2542 )
2543 else:
2544 to_add = None
2546 # TODO: Find the case where "__getitem__" is used
2547 if hasattr(to_add, "__iter__") or hasattr(
2548 to_add, "__getitem__"
2549 ):
2550 body.extend(to_add)
2551 elif to_add == None:
2552 body.extend([])
2553 else:
2554 raise TypeError(
2555 f"Unexpected type in visit_FuncDef: {type(to_add)}"
2556 )
2558 # Merge keys from prev_scope not in cur_scope into cur_scope
2559 # merge_dicts(prev_scope_id_dict, curr_scope_id_dict)
2561 # TODO: Decorators? Returns? Type_comment?
2562 ref = [
2563 SourceRef(
2564 source_file_name=self.filenames[-1],
2565 col_start=node.col_offset,
2566 col_end=node.end_col_offset,
2567 row_start=node.lineno,
2568 row_end=node.end_lineno,
2569 )
2570 ]
2572 # "Revert" the enclosing scope dictionary to what it was before we went into this function
2573 # since none of the variables within here should exist outside of here..?
2574 # TODO: this might need to be different, since Python variables can exist outside of a scope??
2575 prev_scope_id_dict = copy.deepcopy(prev_scope_id_dict_copy)
2577 prev_func_args = copy.deepcopy(self.curr_func_args)
2578 self.curr_func_args = copy.deepcopy(prev_func_args)
2580 # Global level (i.e. module level) functions have their module names appended to them, we make sure
2581 # we have the correct name depending on whether or not we're visiting a global
2582 # level function or a function enclosed within another function
2583 if node.name in prev_scope_id_dict.keys():
2584 if self.legacy:
2585 return [FunctionDef(node.name, args, body, source_refs=ref)]
2586 else:
2587 return [
2588 FunctionDef(
2589 Name(
2590 node.name,
2591 prev_scope_id_dict[node.name],
2592 source_refs=ref,
2593 ),
2594 args,
2595 body,
2596 source_refs=ref,
2597 )
2598 ]
2599 else:
2600 unique_name = construct_unique_name(self.filenames[-1], node.name)
2601 if unique_name in prev_scope_id_dict.keys():
2602 if self.legacy:
2603 return [
2604 FunctionDef(node.name, args, body, source_refs=ref)
2605 ]
2606 else:
2607 return [
2608 FunctionDef(
2609 Name(
2610 node.name,
2611 prev_scope_id_dict[unique_name],
2612 source_refs=ref,
2613 ),
2614 args,
2615 body,
2616 source_refs=ref,
2617 )
2618 ]
2619 else:
2620 self.insert_next_id(prev_scope_id_dict, unique_name)
2621 return [
2622 FunctionDef(
2623 Name(
2624 node.name,
2625 prev_scope_id_dict[unique_name],
2626 source_refs=ref,
2627 ),
2628 args,
2629 body,
2630 source_refs=ref,
2631 )
2632 ]
2634 @visit.register
2635 def visit_Lambda(
2636 self,
2637 node: ast.Lambda,
2638 prev_scope_id_dict,
2639 curr_scope_id_dict,
2640 ):
2641 """Visits a PyAST Lambda node. Which is used for a Python Lambda
2642 function definition. It works pretty analogously to the FunctionDef
2643 node visitor. It also returns a FunctionDef node like the PyAST
2644 FunctionDef node visitor.
2646 Args:
2647 node (ast.Lambda): A PyAST lambda function definition node
2649 Returns:
2650 FunctionDef: A CAST Function Definition node
2652 """
2654 curr_scope_id_dict = {}
2656 args = []
2657 # TODO: Correct typing instead of just 'float'
2658 if len(node.args.args) > 0:
2659 for arg in node.args.args:
2660 self.insert_next_id(curr_scope_id_dict, arg.arg)
2662 args.append(
2663 Var(
2664 Name(
2665 arg.arg,
2666 id=curr_scope_id_dict[arg.arg],
2667 source_refs=[
2668 SourceRef(
2669 self.filenames[-1],
2670 arg.col_offset,
2671 arg.end_col_offset,
2672 arg.lineno,
2673 arg.end_lineno,
2674 )
2675 ],
2676 ),
2677 "float", # TODO: Correct typing instead of just 'float'
2678 source_refs=[
2679 SourceRef(
2680 self.filenames[-1],
2681 arg.col_offset,
2682 arg.end_col_offset,
2683 arg.lineno,
2684 arg.end_lineno,
2685 )
2686 ],
2687 )
2688 )
2690 body = self.visit(node.body, prev_scope_id_dict, curr_scope_id_dict)
2692 ref = [
2693 SourceRef(
2694 source_file_name=self.filenames[-1],
2695 col_start=node.col_offset,
2696 col_end=node.end_col_offset,
2697 row_start=node.lineno,
2698 row_end=node.end_lineno,
2699 )
2700 ]
2701 # TODO: add an ID for lambda name
2702 if self.legacy:
2703 return [FunctionDef("LAMBDA", args, body, source_refs=ref)]
2704 else:
2705 lambda_name = f"%lambda{self.lambda_count}"
2706 self.lambda_count += 1
2707 lambda_id = -1 # TODO
2709 name_node = Name(lambda_name, lambda_id, source_refs=ref)
2710 self.generated_fns.append(FunctionDef(name_node, args, body, source_refs=ref))
2712 # NOTE: What should the arguments be?
2713 to_ret = [Call(func=Name(lambda_name, lambda_id, source_refs=ref),arguments=args,source_refs=ref)]
2715 return to_ret
2717 @visit.register
2718 def visit_ListComp(
2719 self,
2720 node: ast.ListComp,
2721 prev_scope_id_dict,
2722 curr_scope_id_dict,
2723 ):
2724 """Visits a PyAST ListComp node, which are used for Python list comprehensions.
2725 List comprehensions generate a list from some generator expression.
2727 Args:
2728 node (ast.ListComp): A PyAST list comprehension node
2730 Returns:
2731 Loop:
2732 """
2734 ref = [
2735 self.filenames[-1],
2736 node.col_offset,
2737 node.end_col_offset,
2738 node.lineno,
2739 node.end_lineno,
2740 ]
2742 temp_list_name = f"list__temp_"
2743 temp_assign = ast.Assign(
2744 targets=[
2745 ast.Name(
2746 id=temp_list_name,
2747 ctx=ast.Store(),
2748 col_offset=ref[1],
2749 end_col_offset=ref[2],
2750 lineno=ref[3],
2751 end_lineno=ref[4],
2752 )
2753 ],
2754 value=ast.List(
2755 elts=[],
2756 col_offset=ref[1],
2757 end_col_offset=ref[2],
2758 lineno=ref[3],
2759 end_lineno=ref[4],
2760 ),
2761 type_comment=None,
2762 col_offset=ref[1],
2763 end_col_offset=ref[2],
2764 lineno=ref[3],
2765 end_lineno=ref[4],
2766 )
2768 generators = node.generators
2769 first_gen = generators[-1]
2770 i = len(generators) - 2
2772 # Constructs the Python AST for the innermost loop in the list comprehension
2773 if len(first_gen.ifs) > 0:
2774 innermost_loop_body = [
2775 ast.If(
2776 test=first_gen.ifs[0],
2777 body=[
2778 ast.Expr(
2779 value=ast.Call(
2780 func=ast.Attribute(
2781 value=ast.Name(
2782 id=temp_list_name,
2783 ctx=ast.Load(),
2784 col_offset=ref[1],
2785 end_col_offset=ref[2],
2786 lineno=ref[3],
2787 end_lineno=ref[4],
2788 ),
2789 attr="append",
2790 ctx=ast.Load(),
2791 col_offset=ref[1],
2792 end_col_offset=ref[2],
2793 lineno=ref[3],
2794 end_lineno=ref[4],
2795 ),
2796 args=[node.elt],
2797 keywords=[],
2798 col_offset=ref[1],
2799 end_col_offset=ref[2],
2800 lineno=ref[3],
2801 end_lineno=ref[4],
2802 ),
2803 col_offset=ref[1],
2804 end_col_offset=ref[2],
2805 lineno=ref[3],
2806 end_lineno=ref[4],
2807 )
2808 ],
2809 orelse=[],
2810 col_offset=ref[1],
2811 end_col_offset=ref[2],
2812 lineno=ref[3],
2813 end_lineno=ref[4],
2814 )
2815 ]
2816 else:
2817 innermost_loop_body = [
2818 ast.Expr(
2819 value=ast.Call(
2820 func=ast.Attribute(
2821 value=ast.Name(
2822 id=temp_list_name,
2823 ctx=ast.Load(),
2824 col_offset=ref[1],
2825 end_col_offset=ref[2],
2826 lineno=ref[3],
2827 end_lineno=ref[4],
2828 ),
2829 attr="append",
2830 ctx=ast.Load(),
2831 col_offset=ref[1],
2832 end_col_offset=ref[2],
2833 lineno=ref[3],
2834 end_lineno=ref[4],
2835 ),
2836 args=[node.elt],
2837 keywords=[],
2838 col_offset=ref[1],
2839 end_col_offset=ref[2],
2840 lineno=ref[3],
2841 end_lineno=ref[4],
2842 ),
2843 col_offset=ref[1],
2844 end_col_offset=ref[2],
2845 lineno=ref[3],
2846 end_lineno=ref[4],
2847 )
2848 ]
2850 loop_collection = [
2851 ast.For(
2852 target=self.identify_piece(
2853 first_gen.target, prev_scope_id_dict, curr_scope_id_dict
2854 ),
2855 iter=first_gen.iter,
2856 # iter=self.identify_piece(first_gen.iter, prev_scope_id_dict, curr_scope_id_dict),
2857 body=innermost_loop_body,
2858 orelse=[],
2859 col_offset=ref[1],
2860 end_col_offset=ref[2],
2861 lineno=ref[3],
2862 end_lineno=ref[4],
2863 )
2864 ]
2866 # Every other loop in the list comprehension wraps itself around the previous loop that we
2867 # added
2868 while i >= 0:
2869 curr_gen = generators[i]
2870 if len(curr_gen.ifs) > 0:
2871 # TODO: if multiple ifs exist per a single generator then we have to expand this
2872 curr_if = curr_gen.ifs[0]
2873 next_loop = ast.For(
2874 target=self.identify_piece(
2875 curr_gen.target, curr_scope_id_dict, prev_scope_id_dict
2876 ),
2877 iter=self.identify_piece(
2878 curr_gen.iter, curr_scope_id_dict, prev_scope_id_dict
2879 ),
2880 body=[
2881 ast.If(
2882 test=curr_if,
2883 body=[loop_collection[0]],
2884 orelse=[],
2885 col_offset=ref[1],
2886 end_col_offset=ref[2],
2887 lineno=ref[3],
2888 end_lineno=ref[4],
2889 )
2890 ],
2891 orelse=[],
2892 col_offset=ref[1],
2893 end_col_offset=ref[2],
2894 lineno=ref[3],
2895 end_lineno=ref[4],
2896 )
2898 else:
2899 next_loop = ast.For(
2900 target=self.identify_piece(
2901 curr_gen.target, curr_scope_id_dict, prev_scope_id_dict
2902 ),
2903 iter=self.identify_piece(
2904 curr_gen.iter, curr_scope_id_dict, prev_scope_id_dict
2905 ),
2906 body=[loop_collection[0]],
2907 orelse=[],
2908 col_offset=ref[1],
2909 end_col_offset=ref[2],
2910 lineno=ref[3],
2911 end_lineno=ref[4],
2912 )
2914 loop_collection.insert(0, next_loop)
2915 i = i - 1
2917 temp_cast = self.visit(
2918 temp_assign, prev_scope_id_dict, curr_scope_id_dict
2919 )
2920 loop_cast = self.visit(
2921 loop_collection[0], prev_scope_id_dict, curr_scope_id_dict
2922 )
2924 ref = [
2925 SourceRef(
2926 source_file_name=self.filenames[-1],
2927 col_start=node.col_offset,
2928 col_end=node.end_col_offset,
2929 row_start=node.lineno,
2930 row_end=node.end_lineno,
2931 )
2932 ]
2934 # TODO: arguments for a comprehension, IDs
2935 return_cast = [ModelReturn(value=Var(val=Name(name=temp_list_name, source_refs=ref), source_refs=ref), source_refs=ref)]
2937 comp_func_name = f"%comprehension_list_{self.list_comp_count}"
2938 self.list_comp_count += 1
2939 comp_func_id = -1 #TODO
2941 name_node = Name(comp_func_name, comp_func_id, source_refs=ref)
2942 func_def_cast = FunctionDef(name=name_node, func_args=[], body=temp_cast+loop_cast+return_cast, source_refs=ref)
2944 self.generated_fns.append(func_def_cast)
2946 to_ret = [Call(func=Name(comp_func_name, comp_func_id, source_refs=ref),arguments=[],source_refs=ref)]
2948 return to_ret
2950 @visit.register
2951 def visit_DictComp(
2952 self,
2953 node: ast.DictComp,
2954 prev_scope_id_dict,
2955 curr_scope_id_dict,
2956 ):
2957 ref = [
2958 self.filenames[-1],
2959 node.col_offset,
2960 node.end_col_offset,
2961 node.lineno,
2962 node.end_lineno,
2963 ]
2965 # node (ast.DictComp)
2966 # key - what makes the keys
2967 # value - what makes the valuedds
2968 # generators - list of 'comprehension' nodes
2970 temp_dict_name = f"dict__temp_"
2972 generators = node.generators
2973 first_gen = generators[-1]
2974 i = len(generators) - 2
2975 temp_assign = ast.Assign(
2976 targets=[
2977 ast.Name(
2978 id=temp_dict_name,
2979 ctx=ast.Store(),
2980 col_offset=ref[1],
2981 end_col_offset=ref[2],
2982 lineno=ref[3],
2983 end_lineno=ref[4],
2984 )
2985 ],
2986 value=ast.Dict(
2987 keys=[],
2988 values=[],
2989 col_offset=ref[1],
2990 end_col_offset=ref[2],
2991 lineno=ref[3],
2992 end_lineno=ref[4],
2993 ),
2994 type_comment=None,
2995 col_offset=ref[1],
2996 end_col_offset=ref[2],
2997 lineno=ref[3],
2998 end_lineno=ref[4],
2999 )
3001 # Constructs the Python AST for the innermost loop in the dict comprehension
3002 if len(first_gen.ifs) > 0:
3003 innermost_loop_body = ast.If(
3004 test=first_gen.ifs[0],
3005 body=[
3006 ast.Assign(
3007 targets=[
3008 ast.Subscript(
3009 value=ast.Name(
3010 id=temp_dict_name,
3011 ctx=ast.Load(),
3012 col_offset=ref[1],
3013 end_col_offset=ref[2],
3014 lineno=ref[3],
3015 end_lineno=ref[4],
3016 ),
3017 slice=node.key,
3018 ctx=ast.Store(),
3019 col_offset=ref[1],
3020 end_col_offset=ref[2],
3021 lineno=ref[3],
3022 end_lineno=ref[4],
3023 )
3024 ],
3025 value=node.value,
3026 type_comment=None,
3027 col_offset=ref[1],
3028 end_col_offset=ref[2],
3029 lineno=ref[3],
3030 end_lineno=ref[4],
3031 )
3032 ],
3033 orelse=[],
3034 col_offset=ref[1],
3035 end_col_offset=ref[2],
3036 lineno=ref[3],
3037 end_lineno=ref[4],
3038 )
3039 else:
3040 innermost_loop_body = ast.Assign(
3041 targets=[
3042 ast.Subscript(
3043 value=ast.Name(
3044 id=temp_dict_name,
3045 ctx=ast.Load(),
3046 col_offset=ref[1],
3047 end_col_offset=ref[2],
3048 lineno=ref[3],
3049 end_lineno=ref[4],
3050 ),
3051 slice=node.key,
3052 ctx=ast.Store(),
3053 col_offset=ref[1],
3054 end_col_offset=ref[2],
3055 lineno=ref[3],
3056 end_lineno=ref[4],
3057 )
3058 ],
3059 value=node.value,
3060 type_comment=None,
3061 col_offset=ref[1],
3062 end_col_offset=ref[2],
3063 lineno=ref[3],
3064 end_lineno=ref[4],
3065 )
3067 loop_collection = [
3068 ast.For(
3069 target=self.identify_piece(
3070 first_gen.target, prev_scope_id_dict, curr_scope_id_dict
3071 ),
3072 iter=first_gen.iter,
3073 # iter=self.identify_piece(first_gen.iter, prev_scope_id_dict, curr_scope_id_dict),
3074 body=[innermost_loop_body],
3075 orelse=[],
3076 col_offset=ref[1],
3077 end_col_offset=ref[2],
3078 lineno=ref[3],
3079 end_lineno=ref[4],
3080 )
3081 ]
3083 # Every other loop in the list comprehension wraps itself around the previous loop that we
3084 # added
3085 while i >= 0:
3086 curr_gen = generators[i]
3087 if len(curr_gen.ifs) > 0:
3088 # TODO: if multiple ifs exist per a single generator then we have to expand this
3089 curr_if = curr_gen.ifs[0]
3090 next_loop = ast.For(
3091 target=self.identify_piece(
3092 curr_gen.target, prev_scope_id_dict, curr_scope_id_dict
3093 ),
3094 iter=curr_gen.iter,
3095 # iter=self.identify_piece(curr_gen.iter, prev_scope_id_dict, curr_scope_id_dict),
3096 body=[
3097 ast.If(
3098 test=curr_if,
3099 body=[loop_collection[0]],
3100 orelse=[],
3101 col_offset=ref[1],
3102 end_col_offset=ref[2],
3103 lineno=ref[3],
3104 end_lineno=ref[4],
3105 )
3106 ],
3107 orelse=[],
3108 col_offset=ref[1],
3109 end_col_offset=ref[2],
3110 lineno=ref[3],
3111 end_lineno=ref[4],
3112 )
3113 else:
3114 next_loop = ast.For(
3115 target=self.identify_piece(
3116 curr_gen.target, prev_scope_id_dict, curr_scope_id_dict
3117 ),
3118 iter=curr_gen.iter,
3119 # iter=self.identify_piece(curr_gen.iter, prev_scope_id_dict, curr_scope_id_dict),
3120 body=[loop_collection[0]],
3121 orelse=[],
3122 col_offset=ref[1],
3123 end_col_offset=ref[2],
3124 lineno=ref[3],
3125 end_lineno=ref[4],
3126 )
3127 loop_collection.insert(0, next_loop)
3128 i = i - 1
3130 ref = [
3131 SourceRef(
3132 source_file_name=self.filenames[-1],
3133 col_start=node.col_offset,
3134 col_end=node.end_col_offset,
3135 row_start=node.lineno,
3136 row_end=node.end_lineno,
3137 )
3138 ]
3140 temp_cast = self.visit(
3141 temp_assign, prev_scope_id_dict, curr_scope_id_dict
3142 )
3143 loop_cast = self.visit(
3144 loop_collection[0], prev_scope_id_dict, curr_scope_id_dict
3145 )
3147 # TODO: Arguments for comprehension, IDs
3148 return_cast = [ModelReturn(value=Var(val=Name(name=temp_dict_name, id=-1, source_refs=ref), source_refs=ref), source_refs=ref)]
3150 comp_func_name = f"%comprehension_dict_{self.dict_comp_count}"
3151 self.dict_comp_count += 1
3152 comp_func_id = -1 # TODO
3154 name_node = Name(comp_func_name, comp_func_id, source_refs=ref)
3155 func_def_cast = FunctionDef(name=name_node, func_args=[], body=temp_cast+loop_cast+return_cast, source_refs=ref)
3157 self.generated_fns.append(func_def_cast)
3159 to_ret = [Call(func=Name(comp_func_name, comp_func_id, source_refs=ref),arguments=[],source_refs=ref)]
3161 return to_ret
3163 @visit.register
3164 def visit_If(
3165 self, node: ast.If, prev_scope_id_dict, curr_scope_id_dict
3166 ):
3167 """Visits a PyAST If node. Which is used to represent If statements.
3168 We visit each of the pieces accordingly and construct the CAST
3169 representation. else/elif statements are stored in the 'orelse' field,
3170 if there are any.
3172 Args:
3173 node (ast.If): A PyAST If node.
3175 Returns:
3176 ModelIf: A CAST If statement node.
3177 """
3179 # node_test = self.visit(
3180 # node.test, prev_scope_id_dict, curr_scope_id_dict
3181 # )
3182 node_test = self.create_cond(
3183 node, prev_scope_id_dict, curr_scope_id_dict
3184 )
3186 node_body = []
3187 if len(node.body) > 0:
3188 for piece in node.body:
3189 node_body.extend(
3190 self.visit(piece, prev_scope_id_dict, curr_scope_id_dict)
3191 )
3193 node_orelse = []
3194 if len(node.orelse) > 0:
3195 for piece in node.orelse:
3196 node_orelse.extend(
3197 self.visit(piece, prev_scope_id_dict, curr_scope_id_dict)
3198 )
3200 ref = [
3201 SourceRef(
3202 source_file_name=self.filenames[-1],
3203 col_start=node.col_offset,
3204 col_end=node.end_col_offset,
3205 row_start=node.lineno,
3206 row_end=node.end_lineno,
3207 )
3208 ]
3211 if isinstance(node_test, list):
3212 return [ModelIf(node_test[0], node_body, node_orelse, source_refs=ref)]
3213 else:
3214 return [ModelIf(node_test, node_body, node_orelse, source_refs=ref)]
3216 @visit.register
3217 def visit_Global(
3218 self,
3219 node: ast.Global,
3220 prev_scope_id_dict,
3221 curr_scope_id_dict,
3222 ):
3223 """Visits a PyAST Global node.
3224 What this does is write in the IDs for variables that are
3225 explicitly declared as global within a scope using the global keyword
3226 as follows
3227 global x [, y, z, etc..]
3229 Args:
3230 node (ast.Global): A PyAST Global node
3231 prev_scope_id_dict (Dict): Dictionary containing the scope's current variable : ID maps
3233 Returns:
3234 List: empty list
3235 """
3237 for v in node.names:
3238 unique_name = construct_unique_name(self.filenames[-1], v)
3239 curr_scope_id_dict[unique_name] = self.global_identifier_dict[
3240 unique_name
3241 ]
3242 return []
3244 @visit.register
3245 def visit_IfExp(
3246 self,
3247 node: ast.IfExp,
3248 prev_scope_id_dict,
3249 curr_scope_id_dict,
3250 ):
3251 """Visits a PyAST IfExp node, which is Python's ternary operator.
3252 The node gets translated into a CAST ModelIf node by visiting all its parts,
3253 since IfExp behaves like an If statement.
3255 # TODO: Rethink how this is done to better reflect
3256 - ternary for assignments
3257 - ternary in function call arguments
3259 # NOTE: Do we want to treat this as a conditional block in GroMEt? But it shouldn't show up in the expression tree
3261 Args:
3262 node (ast.IfExp): [description]
3263 """
3265 node_test = self.visit(
3266 node.test, prev_scope_id_dict, curr_scope_id_dict
3267 )
3268 node_body = self.visit(
3269 node.body, prev_scope_id_dict, curr_scope_id_dict
3270 )
3271 node_orelse = self.visit(
3272 node.orelse, prev_scope_id_dict, curr_scope_id_dict
3273 )
3274 ref = [
3275 SourceRef(
3276 source_file_name=self.filenames[-1],
3277 col_start=node.col_offset,
3278 col_end=node.end_col_offset,
3279 row_start=node.lineno,
3280 row_end=node.end_lineno,
3281 )
3282 ]
3284 return [ModelIf(node_test[0], node_body, node_orelse, source_refs=ref)]
3286 @visit.register
3287 def visit_Import(
3288 self,
3289 node: ast.Import,
3290 prev_scope_id_dict,
3291 curr_scope_id_dict,
3292 ):
3293 """Visits a PyAST Import node, which is used for importing libraries
3294 that are used in programs. In particular, it's imports in the form of
3295 'import X', where X is some library.
3297 Args:
3298 node (ast.Import): A PyAST Import node
3300 Returns:
3301 """
3302 ref = [
3303 SourceRef(
3304 source_file_name=self.filenames[-1],
3305 col_start=node.col_offset,
3306 col_end=node.end_col_offset,
3307 row_start=node.lineno,
3308 row_end=node.end_lineno,
3309 )
3310 ]
3312 names = node.names
3313 to_ret = []
3314 for alias in names:
3315 as_name = alias.asname
3316 orig_name = alias.name
3318 # Construct the path of the module, relative to where we are at
3319 # TODO: (Still have to handle things like '..')
3320 name = alias.name
3322 # module1.x, module2.x
3323 # {module1: {x: 1}, module2: {x: 4}}
3325 # For cases like 'import module as something_else'
3326 # We note the alias that the import uses for this module
3327 # Qualify names
3328 if as_name is not None:
3329 self.aliases[as_name] = orig_name
3330 name = alias.asname
3332 # TODO: Could use a flag to mark a Module as an import (old)
3333 if orig_name in BUILTINS or find_std_lib_module(orig_name):
3334 self.insert_next_id(self.global_identifier_dict, name)
3335 to_ret.append(
3336 ModelImport(
3337 name=orig_name,
3338 alias=as_name,
3339 symbol=None,
3340 all=False,
3341 source_refs=ref,
3342 )
3343 )
3344 return to_ret
3346 @visit.register
3347 def visit_ImportFrom(
3348 self,
3349 node: ast.ImportFrom,
3350 prev_scope_id_dict,
3351 curr_scope_id_dict,
3352 ):
3353 """Visits a PyAST ImportFrom node, which is used for importing libraries
3354 that are used in programs. In particular, it's imports in the form of
3355 'import X', where X is some library.
3357 Args:
3358 node (ast.Import): A PyAST Import node
3360 Returns:
3361 """
3363 # Construct the path of the module, relative to where we are at
3364 # (TODO: Still have to handle things like '..')
3365 # TODO: What about importing individual functions from a module M
3366 # that call other functions from that same module M
3367 ref = [
3368 SourceRef(
3369 source_file_name=self.filenames[-1],
3370 col_start=node.col_offset,
3371 col_end=node.end_col_offset,
3372 row_start=node.lineno,
3373 row_end=node.end_lineno,
3374 )
3375 ]
3377 name = node.module
3378 if name in self.aliases:
3379 name = self.aliases[name]
3381 aliases = node.names
3382 to_ret = []
3383 for (
3384 alias
3385 ) in (
3386 aliases
3387 ): # Iterate through the symbols that are being imported and create individual imports for each
3388 if alias.asname is not None:
3389 self.aliases[alias.asname] = alias.name
3391 if name in BUILTINS or find_std_lib_module(name):
3392 if alias.name == "*":
3393 to_ret.append(
3394 ModelImport(
3395 name=name,
3396 alias=None,
3397 symbol=None,
3398 all=True,
3399 source_refs=ref,
3400 )
3401 )
3402 else:
3403 to_ret.append(
3404 ModelImport(
3405 name=name,
3406 alias=None,
3407 symbol=alias.name,
3408 all=False,
3409 source_refs=ref,
3410 )
3411 )
3412 else: # User defined module import
3413 if alias.name == "*":
3414 to_ret.append(
3415 ModelImport(
3416 name=name,
3417 alias=None,
3418 symbol=None,
3419 all=True,
3420 source_refs=ref,
3421 )
3422 )
3423 else:
3424 to_ret.append(
3425 ModelImport(
3426 name=name,
3427 alias=None,
3428 symbol=alias.name,
3429 all=False,
3430 source_refs=ref,
3431 )
3432 )
3434 return to_ret
3436 @visit.register
3437 def visit_List(
3438 self,
3439 node: ast.List,
3440 prev_scope_id_dict,
3441 curr_scope_id_dict,
3442 ):
3443 """Visits a PyAST List node. Which is used to represent Python lists.
3445 Args:
3446 node (ast.List): A PyAST List node.
3448 Returns:
3449 List: A CAST List node.
3450 """
3452 source_code_data_type = ["Python", "3.8", "List"]
3453 ref = [
3454 SourceRef(
3455 source_file_name=self.filenames[-1],
3456 col_start=node.col_offset,
3457 col_end=node.end_col_offset,
3458 row_start=node.lineno,
3459 row_end=node.end_lineno,
3460 )
3461 ]
3462 # TODO: How to handle constructors with variables?
3463 if len(node.elts) > 0:
3464 to_ret = []
3465 for piece in node.elts:
3466 to_ret.extend(
3467 self.visit(piece, prev_scope_id_dict, curr_scope_id_dict)
3468 )
3469 # TODO: How to represent computations like '[0.0] * 1000' in some kind of type constructing system
3470 # and then how could we store that in these CASTLiteralValue nodes?
3471 return [
3472 CASTLiteralValue(
3473 StructureType.LIST, to_ret, source_code_data_type, ref
3474 )
3475 ]
3476 # return [List(to_ret,source_refs=ref)]
3477 else:
3478 return [
3479 CASTLiteralValue(
3480 StructureType.LIST, [], source_code_data_type, ref
3481 )
3482 ]
3483 # return [List([],source_refs=ref)]
3485 @visit.register
3486 def visit_Module(
3487 self,
3488 node: ast.Module,
3489 prev_scope_id_dict,
3490 curr_scope_id_dict,
3491 ):
3492 """Visits a PyAST Module node. This is the starting point of CAST Generation,
3493 as the body of the Module node (usually) contains the entire Python
3494 program.
3496 Args:
3497 node (ast.Module): A PyAST Module node.
3499 Returns:
3500 Module: A CAST Module node.
3501 """
3503 # Visit all the nodes and make a Module object out of them
3504 body = []
3505 funcs = []
3506 ref = [
3507 SourceRef(
3508 source_file_name=self.filenames[-1],
3509 col_start=-1,
3510 col_end=-1,
3511 row_start=-1,
3512 row_end=-1,
3513 )
3514 ]
3515 self.module_stack.append(node)
3517 # Attempt to capture all global variable names
3518 # before we do any function definitions
3519 # (functions can use global variables so they need them all available)
3520 for line in node.body:
3521 if isinstance(line, ast.Assign):
3522 names = get_node_name(line)
3524 for var_name in names:
3525 unique_name = construct_unique_name(
3526 self.filenames[-1], var_name
3527 )
3528 self.insert_next_id(self.global_identifier_dict, unique_name)
3530 merge_dicts(self.global_identifier_dict, curr_scope_id_dict)
3531 merge_dicts(curr_scope_id_dict, prev_scope_id_dict)
3532 for piece in node.body:
3533 if isinstance(piece, ast.FunctionDef):
3534 unique_name = construct_unique_name(
3535 self.filenames[-1], piece.name
3536 )
3537 self.insert_next_id(curr_scope_id_dict, unique_name)
3538 prev_scope_id_dict[unique_name] = curr_scope_id_dict[
3539 unique_name
3540 ]
3542 # If the current node is literally just a string literal
3543 # then that means it's a docstring and we don't need it here
3544 if not check_expr_str(piece):
3545 to_add = self.visit(piece, prev_scope_id_dict, curr_scope_id_dict)
3546 else:
3547 to_add = []
3549 # Global variables (which come about from assignments at the module level)
3550 # need to have their identifier names set correctly so they can be
3551 # accessed appropriately later on
3552 # We check if we just visited an assign and fix its key/value pair in the dictionary
3553 # So instead of
3554 # "var_name" -> ID
3555 # It becomes
3556 # "module_name.var_name" -> ID
3557 # in the dictionary
3558 # If an assign happens at the global level, then we must also make sure to
3559 # Update the global dictionary at this time so that the IDs are defined
3560 # and are correct
3561 if isinstance(piece, ast.Assign):
3562 names = get_node_name(to_add[0])
3564 for var_name in names:
3565 #temp_id = curr_scope_id_dict[var_name]
3566 del curr_scope_id_dict[var_name]
3567 #unique_name = construct_unique_name(
3568 # self.filenames[-1], var_name
3569 #)
3570 #curr_scope_id_dict[unique_name] = temp_id
3571 #merge_dicts(
3572 # curr_scope_id_dict, self.global_identifier_dict
3573 #)
3575 if isinstance(to_add, Module):
3576 body.extend([to_add])
3577 else:
3578 body.extend(to_add)
3580 #merge_dicts(curr_scope_id_dict, self.global_identifier_dict)
3581 #merge_dicts(prev_scope_id_dict, curr_scope_id_dict)
3583 # Visit all the functions
3584 #for piece in funcs:
3585 # to_add = self.visit(piece, curr_scope_id_dict, {})
3586 # body.extend(to_add)
3588 self.module_stack.pop()
3589 return Module(
3590 name=self.filenames[-1].split(".")[0], body=self.generated_fns+body, source_refs=ref
3591 )
3593 @visit.register
3594 def visit_Name(
3595 self,
3596 node: ast.Name,
3597 prev_scope_id_dict,
3598 curr_scope_id_dict,
3599 ):
3600 """This visits PyAST Name nodes, which consist of
3601 id: The name of a variable as a string
3602 ctx: The context in which the variable is being used
3604 Args:
3605 node (ast.Name): A PyAST Name node
3607 Returns:
3608 Expr: A CAST Expression node
3610 """
3611 # TODO: Typing so it's not hardcoded to floats
3612 ref = [
3613 SourceRef(
3614 source_file_name=self.filenames[-1],
3615 col_start=node.col_offset,
3616 col_end=node.end_col_offset,
3617 row_start=node.lineno,
3618 row_end=node.end_lineno,
3619 )
3620 ]
3622 if isinstance(node.ctx, ast.Load):
3623 if node.id in self.aliases:
3624 return [Name(self.aliases[node.id], id=-1, source_refs=ref)]
3626 if node.id not in curr_scope_id_dict:
3627 if node.id in prev_scope_id_dict:
3628 curr_scope_id_dict[node.id] = prev_scope_id_dict[node.id]
3629 else:
3630 unique_name = construct_unique_name(
3631 self.filenames[-1], node.id
3632 )
3634 # We can have the very odd case where a variable is used in a function before
3635 # it even exists. To my knowledge this happens in one scenario:
3636 # - A global variable, call it z, is used in a function
3637 # - Before that function is called in Python code, that global variable
3638 # z is set by another module/another piece of code as a global
3639 # (i.e. by doing module_name.z = a value)
3640 # It's not something that is very common (or good) to do, but regardless
3641 # we'll catch it here just in case.
3642 if unique_name not in self.global_identifier_dict.keys():
3643 self.insert_next_id(
3644 self.global_identifier_dict, unique_name
3645 )
3647 curr_scope_id_dict[node.id] = self.global_identifier_dict[
3648 unique_name
3649 ]
3651 return [
3652 Name(node.id, id=curr_scope_id_dict[node.id], source_refs=ref)
3653 ]
3655 if isinstance(node.ctx, ast.Store):
3656 if node.id in self.aliases:
3657 return [
3658 Var(
3659 Name(self.aliases[node.id], id=-1, source_refs=ref),
3660 "float",
3661 source_refs=ref,
3662 )
3663 ]
3665 if node.id not in curr_scope_id_dict:
3666 # We construct the unique name for the case that
3667 # An assignment to a global happens in the global scope
3668 # (i.e. a loop at the global level)
3669 # Check if it's in the previous scope not as a global (general case when in a function)
3670 # then check if it's in the previous scope as a global (when we're at the global scope)
3671 unique_name = construct_unique_name(
3672 self.filenames[-1], node.id
3673 )
3674 if node.id in prev_scope_id_dict:
3675 curr_scope_id_dict[node.id] = prev_scope_id_dict[node.id]
3676 elif unique_name in prev_scope_id_dict:
3677 curr_scope_id_dict[node.id] = prev_scope_id_dict[
3678 unique_name
3679 ]
3680 else:
3681 self.insert_next_id(curr_scope_id_dict, node.id)
3683 return [
3684 Var(
3685 Name(
3686 node.id,
3687 id=curr_scope_id_dict[node.id],
3688 source_refs=ref,
3689 ),
3690 "float",
3691 source_refs=ref,
3692 )
3693 ]
3695 if isinstance(node.ctx, ast.Del):
3696 # TODO: At some point..
3697 raise NotImplementedError()
3699 @visit.register
3700 def visit_Pass(
3701 self,
3702 node: ast.Pass,
3703 prev_scope_id_dict,
3704 curr_scope_id_dict,
3705 ):
3706 """A PyAST Pass visitor, for essentially NOPs."""
3707 source_code_data_type = ["Python", "3.8", "List"]
3708 ref = [
3709 SourceRef(
3710 source_file_name=self.filenames[-1],
3711 col_start=node.col_offset,
3712 col_end=node.end_col_offset,
3713 row_start=node.lineno,
3714 row_end=node.end_lineno,
3715 )
3716 ]
3717 return [
3718 CASTLiteralValue(
3719 StructureType.LIST,
3720 "NotImplemented",
3721 source_code_data_type,
3722 ref,
3723 )
3724 ]
3726 @visit.register
3727 def visit_Raise(
3728 self,
3729 node: ast.Raise,
3730 prev_scope_id_dict,
3731 curr_scope_id_dict,
3732 ):
3733 """A PyAST Raise visitor, for Raising exceptions
3735 TODO: To be implemented.
3736 """
3737 source_code_data_type = ["Python", "3.8", "List"]
3738 ref = [
3739 SourceRef(
3740 source_file_name=self.filenames[-1],
3741 col_start=node.col_offset,
3742 col_end=node.end_col_offset,
3743 row_start=node.lineno,
3744 row_end=node.end_lineno,
3745 )
3746 ]
3748 exc_name = ""
3749 if isinstance(node.exc, ast.Name):
3750 exc_name = node.exc.id
3751 elif isinstance(node.exc, ast.Call):
3752 if isinstance(node.exc.func, ast.Name):
3753 exc_name = node.exc.func.id
3755 raise_id = -1
3756 if "raise" not in self.global_identifier_dict.keys():
3757 raise_id = self.insert_next_id(
3758 self.global_identifier_dict, "raise"
3759 )
3760 else:
3761 raise_id = self.global_identifier_dict["raise"]
3763 return [
3764 Call(
3765 func=Name("raise", raise_id, source_refs=ref),
3766 arguments=[
3767 CASTLiteralValue(
3768 StructureType.LIST,
3769 exc_name,
3770 source_code_data_type,
3771 ref,
3772 )
3773 ],
3774 source_refs=ref,
3775 )
3776 ]
3778 @visit.register
3779 def visit_Return(
3780 self,
3781 node: ast.Return,
3782 prev_scope_id_dict,
3783 curr_scope_id_dict,
3784 ):
3785 """Visits a PyAST Return node and creates a CAST return node
3786 that has one field, which is the expression computing the value
3787 to be returned. The PyAST's value node is visited.
3788 The CAST node is then returned.
3790 Args:
3791 node (ast.Return): A PyAST Return node
3793 Returns:
3794 ModelReturn: A CAST Return node
3795 """
3797 ref = [
3798 SourceRef(
3799 source_file_name=self.filenames[-1],
3800 col_start=node.col_offset,
3801 col_end=node.end_col_offset,
3802 row_start=node.lineno,
3803 row_end=node.end_lineno,
3804 )
3805 ]
3806 if node.value != None:
3807 return [
3808 ModelReturn(
3809 self.visit(
3810 node.value, prev_scope_id_dict, curr_scope_id_dict
3811 )[0],
3812 source_refs=ref,
3813 )
3814 ]
3815 else:
3816 source_code_data_type = ["Python", "3.8", str(type(node.value))]
3817 val = CASTLiteralValue(None, None, source_code_data_type, ref)
3818 return [ModelReturn(val, source_refs=ref)]
3820 @visit.register
3821 def visit_UnaryOp(
3822 self,
3823 node: ast.UnaryOp,
3824 prev_scope_id_dict,
3825 curr_scope_id_dict,
3826 ):
3827 """Visits a PyAST UnaryOp node. Which represents Python unary operations.
3828 A dictionary is used to index into which operation we're doing.
3830 Args:
3831 node (ast.UnaryOp): A PyAST UnaryOp node.
3833 Returns:
3834 UnaryOp: A CAST UnaryOp node.
3835 """
3837 op = get_op(node.op)
3838 operand = node.operand
3840 opd = self.visit(operand, prev_scope_id_dict, curr_scope_id_dict)
3842 ref = [
3843 SourceRef(
3844 source_file_name=self.filenames[-1],
3845 col_start=node.col_offset,
3846 col_end=node.end_col_offset,
3847 row_start=node.lineno,
3848 row_end=node.end_lineno,
3849 )
3850 ]
3852 return [Operator(source_language="Python",
3853 interpreter="Python",
3854 version=get_python_version(),
3855 op=op,
3856 operands=[opd[0]],
3857 source_refs=ref)]
3859 @visit.register
3860 def visit_Set(
3861 self, node: ast.Set, prev_scope_id_dict, curr_scope_id_dict
3862 ):
3863 """Visits a PyAST Set node. Which is used to represent Python sets.
3865 Args:
3866 node (ast.Set): A PyAST Set node.
3868 Returns:
3869 Set: A CAST Set node.
3870 """
3872 source_code_data_type = ["Python", "3.8", "List"]
3873 ref = [
3874 SourceRef(
3875 source_file_name=self.filenames[-1],
3876 col_start=node.col_offset,
3877 col_end=node.end_col_offset,
3878 row_start=node.lineno,
3879 row_end=node.end_lineno,
3880 )
3881 ]
3883 if len(node.elts) > 0:
3884 to_ret = []
3885 for piece in node.elts:
3886 to_ret.extend(
3887 self.visit(piece, prev_scope_id_dict, curr_scope_id_dict)
3888 )
3889 return [
3890 CASTLiteralValue(
3891 StructureType.SET, to_ret, source_code_data_type, ref
3892 )
3893 ]
3894 else:
3895 return [
3896 CASTLiteralValue(
3897 StructureType.SET, to_ret, source_code_data_type, ref
3898 )
3899 ]
3901 @visit.register
3902 def visit_Subscript(
3903 self,
3904 node: ast.Subscript,
3905 prev_scope_id_dict,
3906 curr_scope_id_dict,
3907 ):
3908 """Visits a PyAST Subscript node, which represents subscripting into
3909 a list in Python. A Subscript is either a Slice (i.e. x[0:2]), an
3910 Extended slice (i.e. x[0:2, 3]), or a constant (i.e. x[3]).
3911 In the Slice case, a loop is generated that fetches the correct elements and puts them
3912 into a list.
3913 In the Extended slice case, nested loops are generated as needed to create a final
3914 result list with the selected elements.
3915 In the constant case, we can visit and generate a CAST Subscript in a normal way.
3917 Args:
3918 node (ast.Subscript): A PyAST Subscript node
3920 Returns:
3921 Subscript: A CAST Subscript node
3922 """
3924 # value = self.visit(node.value, prev_scope_id_dict, curr_scope_id_dict)[0]
3925 ref = [
3926 SourceRef(
3927 source_file_name=self.filenames[-1],
3928 col_start=node.col_offset,
3929 col_end=node.end_col_offset,
3930 row_start=node.lineno,
3931 row_end=node.end_lineno,
3932 )
3933 ]
3935 # 'Visit' the slice
3936 slc = node.slice
3937 temp_var = f"generated_index_{self.var_count}"
3938 self.var_count += 1
3940 if isinstance(slc, ast.Slice):
3941 if slc.lower is not None:
3942 start = self.visit(
3943 slc.lower, prev_scope_id_dict, curr_scope_id_dict
3944 )[0]
3945 else:
3946 start = CASTLiteralValue(
3947 value_type=ScalarType.INTEGER,
3948 value=0,
3949 source_code_data_type=["Python", "3.8", "Float"],
3950 source_refs=ref,
3951 )
3953 if slc.upper is not None:
3954 stop = self.visit(
3955 slc.upper, prev_scope_id_dict, curr_scope_id_dict
3956 )[0]
3957 else:
3958 if isinstance(node.value, ast.Call):
3959 if isinstance(node.value.func, ast.Attribute):
3960 stop = Call(
3961 func=Name("len", source_refs=ref),
3962 arguments=[Name(node.value.func.attr, source_refs=ref)],
3963 source_refs=ref,
3964 )
3965 else:
3966 stop = Call(
3967 func=Name("len", source_refs=ref),
3968 arguments=[Name(node.value.func.id, source_refs=ref)],
3969 source_refs=ref,
3970 )
3971 elif isinstance(node.value, ast.Attribute):
3972 stop = Call(
3973 func=Name("len", source_refs=ref),
3974 arguments=[Name(node.value.attr, source_refs=ref)],
3975 source_refs=ref,
3976 )
3977 else:
3978 if isinstance(node.value, ast.Subscript):
3979 id = self.visit(
3980 node.value, prev_scope_id_dict, curr_scope_id_dict
3981 )
3982 else:
3983 id = node.value.id
3984 stop = Call(
3985 func=Name("len", source_refs=ref),
3986 arguments=[Name(id, source_refs=ref)],
3987 source_refs=ref,
3988 )
3990 if slc.step is not None:
3991 step = self.visit(
3992 slc.step, prev_scope_id_dict, curr_scope_id_dict
3993 )[0]
3994 else:
3995 step = CASTLiteralValue(
3996 value_type=ScalarType.INTEGER,
3997 value=1,
3998 source_code_data_type=["Python", "3.8", "Float"],
3999 source_refs=ref,
4000 )
4002 unique_name = construct_unique_name(self.filenames[-1], "slice")
4003 if unique_name not in prev_scope_id_dict.keys():
4004 # If a built-in is called, then it gets added to the global dictionary if
4005 # it hasn't been called before. This is to maintain one consistent ID per built-in
4006 # function
4007 if unique_name not in self.global_identifier_dict.keys():
4008 self.insert_next_id(
4009 self.global_identifier_dict, unique_name
4010 )
4012 prev_scope_id_dict[unique_name] = self.global_identifier_dict[
4013 unique_name
4014 ]
4016 slice_call = Call(
4017 func=Name(
4018 "slice",
4019 id=prev_scope_id_dict[unique_name],
4020 source_refs=ref,
4021 ),
4022 arguments=[start, stop, step],
4023 source_refs=ref,
4024 )
4026 val = self.visit(
4027 node.value, prev_scope_id_dict, curr_scope_id_dict
4028 )[0]
4030 unique_name = construct_unique_name(self.filenames[-1], "_get")
4031 if unique_name not in prev_scope_id_dict.keys():
4032 # If a built-in is called, then it gets added to the global dictionary if
4033 # it hasn't been called before. This is to maintain one consistent ID per built-in
4034 # function
4035 if unique_name not in self.global_identifier_dict.keys():
4036 self.insert_next_id(
4037 self.global_identifier_dict, unique_name
4038 )
4040 prev_scope_id_dict[unique_name] = self.global_identifier_dict[
4041 unique_name
4042 ]
4043 # return[Call(Name("Concatenate", id=prev_scope_id_dict[unique_name], source_refs=ref), str_pieces, source_refs=ref)]
4045 get_call = Call(
4046 func=Name(
4047 "_get", id=prev_scope_id_dict[unique_name], source_refs=ref
4048 ),
4049 arguments=[val, slice_call],
4050 source_refs=ref,
4051 )
4053 return [get_call]
4054 elif isinstance(slc, ast.Index):
4056 val = self.visit(
4057 node.value, prev_scope_id_dict, curr_scope_id_dict
4058 )[0]
4059 slice_val = self.visit(
4060 slc.value, prev_scope_id_dict, curr_scope_id_dict
4061 )[0]
4062 unique_name = construct_unique_name(self.filenames[-1], "_get")
4063 if unique_name not in prev_scope_id_dict.keys():
4064 # If a built-in is called, then it gets added to the global dictionary if
4065 # it hasn't been called before. This is to maintain one consistent ID per built-in
4066 # function
4067 if unique_name not in self.global_identifier_dict.keys():
4068 self.insert_next_id(
4069 self.global_identifier_dict, unique_name
4070 )
4072 prev_scope_id_dict[unique_name] = self.global_identifier_dict[
4073 unique_name
4074 ]
4075 get_call = Call(
4076 func=Name(
4077 "_get", id=prev_scope_id_dict[unique_name], source_refs=ref
4078 ),
4079 arguments=[val, slice_val],
4080 source_refs=ref,
4081 )
4082 return [get_call]
4083 elif isinstance(slc, ast.ExtSlice):
4084 dims = slc.dims
4085 result = []
4086 source_code_data_type = ["Python", "3.8", "List"]
4087 ref = [
4088 SourceRef(
4089 source_file_name=self.filenames[-1],
4090 col_start=node.col_offset,
4091 col_end=node.end_col_offset,
4092 row_start=node.lineno,
4093 row_end=node.end_lineno,
4094 )
4095 ]
4096 return [
4097 CASTLiteralValue(
4098 StructureType.LIST,
4099 "NotImplemented",
4100 source_code_data_type,
4101 ref,
4102 )
4103 ]
4105 # else:
4106 # sl = self.visit(slc, prev_scope_id_dict, curr_scope_id_dict)
4108 @visit.register
4109 def visit_Index(
4110 self,
4111 node: ast.Index,
4112 prev_scope_id_dict,
4113 curr_scope_id_dict,
4114 ):
4115 """Visits a PyAST Index node, which represents the value being used
4116 for an index. This visitor doesn't create its own CAST node, but
4117 returns CAST depending on the value that the Index node holds.
4119 Args:
4120 node (ast.Index): A CAST Index node.
4122 Returns:
4123 AstNode: Depending on what the value of the Index node is,
4124 different CAST nodes are returned.
4125 """
4126 return self.visit(node.value, prev_scope_id_dict, curr_scope_id_dict)
4128 @visit.register
4129 def visit_Tuple(
4130 self,
4131 node: ast.Tuple,
4132 prev_scope_id_dict,
4133 curr_scope_id_dict,
4134 ):
4135 """Visits a PyAST Tuple node. Which is used to represent Python tuple.
4137 Args:
4138 node (ast.Tuple): A PyAST Tuple node.
4140 Returns:
4141 Set: A CAST Tuple node.
4142 """
4144 ref = [
4145 SourceRef(
4146 source_file_name=self.filenames[-1],
4147 col_start=node.col_offset,
4148 col_end=node.end_col_offset,
4149 row_start=node.lineno,
4150 row_end=node.end_lineno,
4151 )
4152 ]
4153 # if len(node.elts) > 0:
4154 to_ret = []
4155 for piece in node.elts:
4156 to_ret.extend(
4157 self.visit(piece, prev_scope_id_dict, curr_scope_id_dict)
4158 )
4159 source_code_data_type = ["Python","3.8","Tuple"]
4160 return [CASTLiteralValue(StructureType.TUPLE, to_ret, source_code_data_type, source_refs=ref)]
4162 @visit.register
4163 def visit_Try(
4164 self, node: ast.Try, prev_scope_id_dict, curr_scope_id_dict
4165 ):
4166 """Visits a PyAST Try node, which represents Try/Except blocks.
4167 These are used for Python's exception handling
4169 Currently, the visitor just bypasses the Try/Except feature and just
4170 generates CAST for the body of the 'Try' block, assuming the exception(s)
4171 are never thrown.
4172 """
4174 body = []
4175 for piece in node.body:
4176 body.extend(
4177 self.visit(piece, prev_scope_id_dict, curr_scope_id_dict)
4178 )
4179 for piece in node.orelse:
4180 body.extend(
4181 self.visit(piece, prev_scope_id_dict, curr_scope_id_dict)
4182 )
4183 for piece in node.finalbody:
4184 body.extend(
4185 self.visit(piece, prev_scope_id_dict, curr_scope_id_dict)
4186 )
4188 return body
4190 @visit.register
4191 def visit_Yield(
4192 self,
4193 node: ast.Yield,
4194 prev_scope_id_dict,
4195 curr_scope_id_dict,
4196 ):
4197 source_code_data_type = ["Python", "3.8", "List"]
4198 ref = [
4199 SourceRef(
4200 source_file_name=self.filenames[-1],
4201 col_start=node.col_offset,
4202 col_end=node.end_col_offset,
4203 row_start=node.lineno,
4204 row_end=node.end_lineno,
4205 )
4206 ]
4207 return [
4208 CASTLiteralValue(
4209 StructureType.LIST,
4210 "NotImplemented",
4211 source_code_data_type,
4212 ref,
4213 )
4214 ]
4216 @visit.register
4217 def visit_Assert(
4218 self,
4219 node: ast.Assert,
4220 prev_scope_id_dict,
4221 curr_scope_id_dict,
4222 ):
4223 source_code_data_type = ["Python", "3.8", "List"]
4224 ref = [
4225 SourceRef(
4226 source_file_name=self.filenames[-1],
4227 col_start=node.col_offset,
4228 col_end=node.end_col_offset,
4229 row_start=node.lineno,
4230 row_end=node.end_lineno,
4231 )
4232 ]
4233 return [
4234 CASTLiteralValue(
4235 StructureType.LIST,
4236 "NotImplemented",
4237 source_code_data_type,
4238 ref,
4239 )
4240 ]
4242 @visit.register
4243 def visit_While(
4244 self,
4245 node: ast.While,
4246 prev_scope_id_dict,
4247 curr_scope_id_dict,
4248 ):
4249 """Visits a PyAST While node, which represents a while loop.
4251 Args:
4252 node (ast.While): a PyAST while node
4254 Returns:
4255 Loop: A CAST loop node, which generically represents both For
4256 loops and While loops.
4257 """
4258 ref = [
4259 SourceRef(
4260 source_file_name=self.filenames[-1],
4261 col_start=node.col_offset,
4262 col_end=node.end_col_offset,
4263 row_start=node.lineno,
4264 row_end=node.end_lineno,
4265 )
4266 ]
4268 # test_cond = self.visit(node.test, prev_scope_id_dict, curr_scope_id_dict)[0]
4269 test = self.create_cond(node, prev_scope_id_dict, curr_scope_id_dict)
4271 # Loops have their own enclosing scopes
4272 curr_scope_copy = copy.deepcopy(curr_scope_id_dict)
4273 merge_dicts(prev_scope_id_dict, curr_scope_id_dict)
4274 loop_body_scope = {}
4275 body = []
4276 for piece in node.body + node.orelse:
4277 to_add = self.visit(piece, curr_scope_id_dict, loop_body_scope)
4278 body.extend(to_add)
4280 curr_scope_id_dict = copy.deepcopy(curr_scope_copy)
4282 # loop_body_fn_def = FunctionDef(name="while_temp", func_args=None, body=body)
4283 # return [Loop(init=[], expr=test, body=loop_body_fn_def, source_refs=ref)]
4284 if isinstance(test, list):
4285 return [Loop(pre=[], expr=test[0], body=body, post=[], source_refs=ref)]
4286 else:
4287 return [Loop(pre=[], expr=test, body=body, post=[], source_refs=ref)]
4289 @visit.register
4290 def visit_With(
4291 self,
4292 node: ast.With,
4293 prev_scope_id_dict,
4294 curr_scope_id_dict,
4295 ):
4296 """Visits a PyAST With node. With nodes are used as follows:
4297 with a as b, c as d:
4298 do things with b and d
4299 To use aliases on variables and operate on them
4300 This visitor unrolls the With block and generates the appropriate cast for the
4301 underlying operations
4303 Args:
4304 node (ast.With): a PyAST with node
4306 Args:
4307 [AstNode]: A list of CAST nodes, representing whatever operations were happening in the With
4308 block before they got unrolled
4310 """
4312 ref = None
4313 variables = []
4314 for item in node.items:
4315 ref = [
4316 SourceRef(
4317 source_file_name=self.filenames[-1],
4318 col_start=node.col_offset,
4319 col_end=node.end_col_offset,
4320 row_start=node.lineno,
4321 row_end=node.end_lineno,
4322 )
4323 ]
4324 if item.optional_vars != None:
4325 l = self.visit(
4326 item.optional_vars, prev_scope_id_dict, curr_scope_id_dict
4327 )
4328 r = self.visit(
4329 item.context_expr, prev_scope_id_dict, curr_scope_id_dict
4330 )
4331 variables.extend(
4332 [Assignment(left=l[0], right=r[0], source_refs=ref)]
4333 )
4334 else:
4335 variables.extend(
4336 [
4337 self.visit(
4338 item.context_expr,
4339 prev_scope_id_dict,
4340 curr_scope_id_dict,
4341 )[0]
4342 ]
4343 )
4345 body = []
4346 for piece in node.body:
4347 body.extend(
4348 self.visit(piece, prev_scope_id_dict, curr_scope_id_dict)
4349 )
4351 return variables + body