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

1import ast 

2from enum import unique 

3import os 

4import copy 

5import sys 

6from functools import singledispatchmethod 

7 

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) 

38 

39from skema.program_analysis.tree_sitter_parsers.build_parsers import INSTALLED_LANGUAGES_FILEPATH 

40 

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 

55 

56 string_version = f"{major}.{minor}.{micro}" 

57 

58 return string_version 

59 

60 

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. 

67 

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] 

75 

76 

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 

83 

84 Returns: 

85 string: A string representing a unique name 

86 

87 """ 

88 return f"{attr_name}.{var_name}" 

89 

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 

96 

97 Current cases for multi-line comments 

98 - FunctionDef 

99 - Module level 

100 - ClassDef level 

101 """ 

102 

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 

106 

107 

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") 

144 

145 

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 } 

175 

176 if type(operator) in ops.keys(): 

177 return ops[type(operator)] 

178 return str(operator) 

179 

180 

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. 

198 

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. 

202 

203 Current Fields: 

204 - Aliases 

205 - Visited 

206 - Filenames 

207 - Classes 

208 - Var_Count 

209 - global_identifier_dict 

210 """ 

211 

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 """ 

233 

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 

247 

248 self.curr_func_args = [] 

249 

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. 

256 

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 

265 

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 

271 

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 

278 

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 

289 

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 

303 

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 

308 

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. 

314 

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 

320 

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 

354 

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. 

358 

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 

365 

366 return None 

367 

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) 

401 

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)] 

418 

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) 

428 

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)] 

445 

446 else: 

447 test = test_cond 

448 

449 return test 

450 

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 

459 

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 ) 

498 

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) 

506 

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 ] 

521 

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 ) 

543 

544 return self.visit(to_visit, prev_scope_id_dict, curr_scope_id_dict) 

545 

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 ] 

572 

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 ] 

595 

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 ] 

622 

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 ] 

649 

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. 

661 

662 Args: 

663 node (ast.Assign): A PyAST Assignment node. 

664 

665 Returns: 

666 Assignment: An assignment CAST node 

667 """ 

668 

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 ] 

678 

679 left = [] 

680 right = [] 

681 

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 ) 

703 

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] 

718 

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] 

732 

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("-------------") 

744 

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(): 

751 

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 ) 

759 

760 prev_scope_id_dict[ 

761 unique_name 

762 ] = self.global_identifier_dict[unique_name] 

763 

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 ] 

780 

781 if isinstance(node.value, ast.Subscript): 

782 

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(): 

789 

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 ) 

797 

798 prev_scope_id_dict[ 

799 unique_name 

800 ] = self.global_identifier_dict[unique_name] 

801 

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 ] 

827 

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 

839 

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 ) 

865 

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(): 

877 

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 ) 

888 

889 prev_scope_id_dict[ 

890 unique_name 

891 ] = self.global_identifier_dict[unique_name] 

892 

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 ) 

906 

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)] 

913 

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 ) 

937 

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)] 

939 

940 return [Assignment(left[0], right[0], source_refs=ref)] 

941 

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. 

951 

952 Args: 

953 node (ast.Attribute): A PyAST Attribute node 

954 

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) 

961 

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 

965 

966 # x.T -> node.value: the node x (Name) -> node.attr is just "T" 

967 

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 ] 

977 

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) 

984 

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) 

1009 

1010 attr_cast = Name( 

1011 name=node.attr, id=curr_scope_id_dict[unique_name], source_refs=ref 

1012 ) 

1013 

1014 return [Attribute(value_cast[0], attr_cast, source_refs=ref)] 

1015 

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. 

1027 

1028 Args: 

1029 node (ast.AugAssign): A PyAST AugAssign node 

1030 

1031 Returns: 

1032 Assign: A CAST Assign node, generated by the Assign visitor. 

1033 """ 

1034 

1035 # Convert AugAssign to regular Assign, and visit 

1036 target = node.target 

1037 value = node.value 

1038 

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 ) 

1098 

1099 return self.visit(convert, prev_scope_id_dict, curr_scope_id_dict) 

1100 

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. 

1110 

1111 Args: 

1112 node (ast.BinOp): A PyAST Binary operator node 

1113 

1114 Returns: 

1115 Operator: A CAST operator node representing a math 

1116 operation (arithmetic or bitwise) 

1117 """ 

1118 

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) 

1122 

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 = [] 

1134 

1135 if len(left) > 1: 

1136 leftb = left[0:-1] 

1137 if len(right) > 1: 

1138 rightb = right[0:-1] 

1139 

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 ) 

1150 

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 

1161 

1162 Args: 

1163 node (ast.Break): An AST Break node 

1164 

1165 Returns: 

1166 ModelBreak: A CAST Break node 

1167 

1168 """ 

1169 

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)] 

1180 

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 

1196 

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]) 

1203 

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 ) 

1213 

1214 tree_root = compare_op 

1215 idx = idx - 2 

1216 else: 

1217 left_child = self.create_binary_compare_tree(node.values[idx]) 

1218 

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 ) 

1228 

1229 tree_root = compare_op 

1230 idx = idx - 1 

1231 

1232 return tree_root 

1233 else: 

1234 op = [node.op] 

1235 

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 ) 

1247 

1248 return compare_op 

1249 print(f"catch type {type(node)}") 

1250 

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. 

1261 

1262 Args: 

1263 node (ast.BoolOp): An AST BoolOp node 

1264 

1265 Returns: 

1266 BinaryOp: A BinaryOp node that is composed of operations connected with 'and'/'or's 

1267 

1268 """ 

1269 

1270 x = self.create_binary_compare_tree(node) 

1271 """ 

1272 print("Root") 

1273 print(x.ops) 

1274 print(x.left) 

1275 print(x.comparators) 

1276 

1277 print("Left") 

1278 print(x.left.ops) 

1279 print(x.left.left) 

1280 print(x.left.comparators) 

1281 

1282 print("Right") 

1283 print(x.comparators[0].ops) 

1284 print(x.comparators[0].left) 

1285 print(x.comparators[0].comparators) 

1286 """ 

1287 

1288 return self.visit(x, prev_scope_id_dict, curr_scope_id_dict) 

1289 

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. 

1301 

1302 Args: 

1303 node (ast.Call): a PyAST Call node 

1304 

1305 Returns: 

1306 Call: A CAST function call node 

1307 """ 

1308 

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 ] 

1321 

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 ) 

1339 

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] 

1350 

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) 

1381 

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] 

1391 

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)) 

1433 

1434 args = func_args + kw_args 

1435 

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 ) 

1471 

1472 prev_scope_id_dict[unique_name] = self.global_identifier_dict[ 

1473 unique_name 

1474 ] 

1475 

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 ) 

1543 

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 ) 

1558 

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) 

1563 

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 ] 

1587 

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 ) 

1647 

1648 return fields 

1649 

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. 

1662 

1663 Args: 

1664 node (ast.ClassDef): A PyAST class definition node 

1665 

1666 Returns: 

1667 ClassDef: A CAST class definition node 

1668 """ 

1669 name = node.name 

1670 self.classes[name] = [] 

1671 

1672 bases = [] 

1673 for base in node.bases: 

1674 bases.extend( 

1675 self.visit(base, prev_scope_id_dict, curr_scope_id_dict) 

1676 ) 

1677 

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}") 

1696 

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 

1705 

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 ) 

1735 

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)] 

1746 

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 

1755 

1756 Args: 

1757 node (ast.Compare): A PyAST Compare node 

1758 

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 ] 

1772 

1773 

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()) 

1782 

1783 

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 ) 

1799 

1800 

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] 

1808 

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) 

1823 

1824 op_idx = op_idx - 1 

1825 idx = idx - 1 

1826 

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) 

1840 

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) 

1845 

1846 return [tree_root] 

1847 

1848 

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. 

1859 

1860 Args: 

1861 node (ast.Constant): A PyAST Constant node 

1862 

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 

1867 

1868 Raises: 

1869 TypeError: If the node's value is something else that isn't 

1870 recognized by the other two cases 

1871 """ 

1872 

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") 

1922 

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 

1932 

1933 Args: 

1934 node (ast.Continue): An AST Continue node 

1935 

1936 Returns: 

1937 ModelContinue: A CAST Continue node 

1938 """ 

1939 

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)] 

1950 

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. 

1959 

1960 Args: 

1961 node (ast.Dict): A PyAST dictionary node 

1962 

1963 Returns: 

1964 Dict: A CAST Dictionary node. 

1965 """ 

1966 # TODO: when a ** shows up in a dictionary 

1967 

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 ) 

1978 

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 ) 

1987 

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] 

1990 

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 ] 

2010 

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 ] 

2020 

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. 

2030 

2031 Args: 

2032 node (ast.Expr): A PyAST Expression node 

2033 

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 """ 

2039 

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) 

2050 

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. 

2061 

2062 Args: 

2063 node (ast.For): A PyAST For loop node. 

2064 

2065 Returns: 

2066 Loop: A CAST loop node, which generically represents both For 

2067 loops and While loops. 

2068 """ 

2069 

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 ] 

2079 

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] 

2086 

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 = {} 

2093 

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 ) 

2102 

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) 

2105 

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 

2110 

2111 iterator_id = self.insert_next_id(curr_scope_id_dict, iterator_name) 

2112 

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"] 

2119 

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"] 

2124 

2125 stop_cond_name = f"sc_{self.var_count}" 

2126 self.var_count += 1 

2127 

2128 stop_cond_id = self.insert_next_id(curr_scope_id_dict, stop_cond_name) 

2129 

2130 iter_var_cast = Var( 

2131 Name(name=iterator_name, id=iterator_id, source_refs=ref), 

2132 "iterator", 

2133 source_refs=ref, 

2134 ) 

2135 

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 ) 

2141 

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 ) 

2151 

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) 

2182 

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 ) 

2201 

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 ] 

2211 

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. 

2221 

2222 Args: 

2223 node (ast.FunctionDef): A PyAST function definition node 

2224 

2225 Returns: 

2226 FunctionDef: A CAST Function Definition node 

2227 """ 

2228 

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) 

2233 

2234 

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 = [] 

2238 

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 ) 

2308 

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 ) 

2345 

2346 pos_idx += 1 

2347 arg_count -= 1 

2348 

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 ) 

2389 

2390 pos_idx += 1 

2391 arg_count -= 1 

2392 default_index += 1 

2393 

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 ) 

2428 

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 ) 

2461 

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 ) 

2494 

2495 functions_to_visit = [] 

2496 

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 

2500 

2501 for piece in node.body: 

2502 if isinstance(piece, ast.Assign): 

2503 names = get_node_name(piece) 

2504 

2505 for var_name in names: 

2506 

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) 

2511 

2512 # unique_name = construct_unique_name( 

2513 # self.filenames[-1], var_name 

2514 # ) 

2515 self.insert_next_id(curr_scope_id_dict, var_name) 

2516 

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: 

2520 

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 

2528 

2529 # print(curr_scope_id_dict) 

2530 # print(prev_scope_id_dict) 

2531 

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) 

2536 

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 

2545 

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 ) 

2557 

2558 # Merge keys from prev_scope not in cur_scope into cur_scope 

2559 # merge_dicts(prev_scope_id_dict, curr_scope_id_dict) 

2560 

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 ] 

2571 

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) 

2576 

2577 prev_func_args = copy.deepcopy(self.curr_func_args) 

2578 self.curr_func_args = copy.deepcopy(prev_func_args) 

2579 

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 ] 

2633 

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. 

2645 

2646 Args: 

2647 node (ast.Lambda): A PyAST lambda function definition node 

2648 

2649 Returns: 

2650 FunctionDef: A CAST Function Definition node 

2651 

2652 """ 

2653 

2654 curr_scope_id_dict = {} 

2655 

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) 

2661 

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 ) 

2689 

2690 body = self.visit(node.body, prev_scope_id_dict, curr_scope_id_dict) 

2691 

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 

2708 

2709 name_node = Name(lambda_name, lambda_id, source_refs=ref) 

2710 self.generated_fns.append(FunctionDef(name_node, args, body, source_refs=ref)) 

2711 

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)] 

2714 

2715 return to_ret 

2716 

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. 

2726 

2727 Args: 

2728 node (ast.ListComp): A PyAST list comprehension node 

2729 

2730 Returns: 

2731 Loop: 

2732 """ 

2733 

2734 ref = [ 

2735 self.filenames[-1], 

2736 node.col_offset, 

2737 node.end_col_offset, 

2738 node.lineno, 

2739 node.end_lineno, 

2740 ] 

2741 

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 ) 

2767 

2768 generators = node.generators 

2769 first_gen = generators[-1] 

2770 i = len(generators) - 2 

2771 

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 ] 

2849 

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 ] 

2865 

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 ) 

2897 

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 ) 

2913 

2914 loop_collection.insert(0, next_loop) 

2915 i = i - 1 

2916 

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 ) 

2923 

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 ] 

2933 

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)] 

2936 

2937 comp_func_name = f"%comprehension_list_{self.list_comp_count}" 

2938 self.list_comp_count += 1 

2939 comp_func_id = -1 #TODO 

2940 

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) 

2943 

2944 self.generated_fns.append(func_def_cast) 

2945 

2946 to_ret = [Call(func=Name(comp_func_name, comp_func_id, source_refs=ref),arguments=[],source_refs=ref)] 

2947 

2948 return to_ret 

2949 

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 ] 

2964 

2965 # node (ast.DictComp) 

2966 # key - what makes the keys 

2967 # value - what makes the valuedds 

2968 # generators - list of 'comprehension' nodes 

2969 

2970 temp_dict_name = f"dict__temp_" 

2971 

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 ) 

3000 

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 ) 

3066 

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 ] 

3082 

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 

3129 

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 ] 

3139 

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 ) 

3146 

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)] 

3149 

3150 comp_func_name = f"%comprehension_dict_{self.dict_comp_count}" 

3151 self.dict_comp_count += 1 

3152 comp_func_id = -1 # TODO 

3153 

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) 

3156 

3157 self.generated_fns.append(func_def_cast) 

3158 

3159 to_ret = [Call(func=Name(comp_func_name, comp_func_id, source_refs=ref),arguments=[],source_refs=ref)] 

3160 

3161 return to_ret 

3162 

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. 

3171 

3172 Args: 

3173 node (ast.If): A PyAST If node. 

3174 

3175 Returns: 

3176 ModelIf: A CAST If statement node. 

3177 """ 

3178 

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 ) 

3185 

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 ) 

3192 

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 ) 

3199 

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 ] 

3209 

3210 

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)] 

3215 

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..] 

3228 

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 

3232 

3233 Returns: 

3234 List: empty list 

3235 """ 

3236 

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 [] 

3243 

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. 

3254 

3255 # TODO: Rethink how this is done to better reflect 

3256 - ternary for assignments 

3257 - ternary in function call arguments 

3258 

3259 # NOTE: Do we want to treat this as a conditional block in GroMEt? But it shouldn't show up in the expression tree 

3260 

3261 Args: 

3262 node (ast.IfExp): [description] 

3263 """ 

3264 

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 ] 

3283 

3284 return [ModelIf(node_test[0], node_body, node_orelse, source_refs=ref)] 

3285 

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. 

3296 

3297 Args: 

3298 node (ast.Import): A PyAST Import node 

3299 

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 ] 

3311 

3312 names = node.names 

3313 to_ret = [] 

3314 for alias in names: 

3315 as_name = alias.asname 

3316 orig_name = alias.name 

3317 

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 

3321 

3322 # module1.x, module2.x 

3323 # {module1: {x: 1}, module2: {x: 4}} 

3324 

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 

3331 

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 

3345 

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. 

3356 

3357 Args: 

3358 node (ast.Import): A PyAST Import node 

3359 

3360 Returns: 

3361 """ 

3362 

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 ] 

3376 

3377 name = node.module 

3378 if name in self.aliases: 

3379 name = self.aliases[name] 

3380 

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 

3390 

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 ) 

3433 

3434 return to_ret 

3435 

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. 

3444 

3445 Args: 

3446 node (ast.List): A PyAST List node. 

3447 

3448 Returns: 

3449 List: A CAST List node. 

3450 """ 

3451 

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)] 

3484 

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. 

3495 

3496 Args: 

3497 node (ast.Module): A PyAST Module node. 

3498 

3499 Returns: 

3500 Module: A CAST Module node. 

3501 """ 

3502 

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) 

3516 

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) 

3523 

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) 

3529 

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 ] 

3541 

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 = [] 

3548 

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]) 

3563 

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 #) 

3574 

3575 if isinstance(to_add, Module): 

3576 body.extend([to_add]) 

3577 else: 

3578 body.extend(to_add) 

3579 

3580 #merge_dicts(curr_scope_id_dict, self.global_identifier_dict) 

3581 #merge_dicts(prev_scope_id_dict, curr_scope_id_dict) 

3582 

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) 

3587 

3588 self.module_stack.pop() 

3589 return Module( 

3590 name=self.filenames[-1].split(".")[0], body=self.generated_fns+body, source_refs=ref 

3591 ) 

3592 

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 

3603 

3604 Args: 

3605 node (ast.Name): A PyAST Name node 

3606 

3607 Returns: 

3608 Expr: A CAST Expression node 

3609 

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 ] 

3621 

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)] 

3625 

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 ) 

3633 

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 ) 

3646 

3647 curr_scope_id_dict[node.id] = self.global_identifier_dict[ 

3648 unique_name 

3649 ] 

3650 

3651 return [ 

3652 Name(node.id, id=curr_scope_id_dict[node.id], source_refs=ref) 

3653 ] 

3654 

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 ] 

3664 

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) 

3682 

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 ] 

3694 

3695 if isinstance(node.ctx, ast.Del): 

3696 # TODO: At some point.. 

3697 raise NotImplementedError() 

3698 

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 ] 

3725 

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 

3734 

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 ] 

3747 

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 

3754 

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"] 

3762 

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 ] 

3777 

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. 

3789 

3790 Args: 

3791 node (ast.Return): A PyAST Return node 

3792 

3793 Returns: 

3794 ModelReturn: A CAST Return node 

3795 """ 

3796 

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)] 

3819 

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. 

3829 

3830 Args: 

3831 node (ast.UnaryOp): A PyAST UnaryOp node. 

3832 

3833 Returns: 

3834 UnaryOp: A CAST UnaryOp node. 

3835 """ 

3836 

3837 op = get_op(node.op) 

3838 operand = node.operand 

3839 

3840 opd = self.visit(operand, prev_scope_id_dict, curr_scope_id_dict) 

3841 

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 ] 

3851 

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)] 

3858 

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. 

3864 

3865 Args: 

3866 node (ast.Set): A PyAST Set node. 

3867 

3868 Returns: 

3869 Set: A CAST Set node. 

3870 """ 

3871 

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 ] 

3882 

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 ] 

3900 

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. 

3916 

3917 Args: 

3918 node (ast.Subscript): A PyAST Subscript node 

3919 

3920 Returns: 

3921 Subscript: A CAST Subscript node 

3922 """ 

3923 

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 ] 

3934 

3935 # 'Visit' the slice 

3936 slc = node.slice 

3937 temp_var = f"generated_index_{self.var_count}" 

3938 self.var_count += 1 

3939 

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 ) 

3952 

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 ) 

3989 

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 ) 

4001 

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 ) 

4011 

4012 prev_scope_id_dict[unique_name] = self.global_identifier_dict[ 

4013 unique_name 

4014 ] 

4015 

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 ) 

4025 

4026 val = self.visit( 

4027 node.value, prev_scope_id_dict, curr_scope_id_dict 

4028 )[0] 

4029 

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 ) 

4039 

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)] 

4044 

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 ) 

4052 

4053 return [get_call] 

4054 elif isinstance(slc, ast.Index): 

4055 

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 ) 

4071 

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 ] 

4104 

4105 # else: 

4106 # sl = self.visit(slc, prev_scope_id_dict, curr_scope_id_dict) 

4107 

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. 

4118 

4119 Args: 

4120 node (ast.Index): A CAST Index node. 

4121 

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) 

4127 

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. 

4136 

4137 Args: 

4138 node (ast.Tuple): A PyAST Tuple node. 

4139 

4140 Returns: 

4141 Set: A CAST Tuple node. 

4142 """ 

4143 

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)] 

4161 

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 

4168 

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 """ 

4173 

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 ) 

4187 

4188 return body 

4189 

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 ] 

4215 

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 ] 

4241 

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. 

4250 

4251 Args: 

4252 node (ast.While): a PyAST while node 

4253 

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 ] 

4267 

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) 

4270 

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) 

4279 

4280 curr_scope_id_dict = copy.deepcopy(curr_scope_copy) 

4281 

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)] 

4288 

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 

4302 

4303 Args: 

4304 node (ast.With): a PyAST with node 

4305 

4306 Args: 

4307 [AstNode]: A list of CAST nodes, representing whatever operations were happening in the With 

4308 block before they got unrolled 

4309 

4310 """ 

4311 

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 ) 

4344 

4345 body = [] 

4346 for piece in node.body: 

4347 body.extend( 

4348 self.visit(piece, prev_scope_id_dict, curr_scope_id_dict) 

4349 ) 

4350 

4351 return variables + body