Coverage for skema/program_analysis/CAST2FN/visitors/cast_to_agraph_visitor.py: 35%

600 statements  

« prev     ^ index     » next       coverage.py v7.5.0, created at 2024-04-30 17:15 +0000

1import networkx as nx 

2 

3from functools import singledispatchmethod 

4from skema.utils.misc import uuid 

5 

6from .cast_visitor import CASTVisitor 

7from skema.program_analysis.CAST2FN.cast import CAST 

8from skema.program_analysis.CAST2FN.model.cast import ( 

9 AstNode, 

10 Assignment, 

11 Attribute, 

12 Call, 

13 FunctionDef, 

14 CASTLiteralValue, 

15 Loop, 

16 ModelBreak, 

17 ModelContinue, 

18 ModelIf, 

19 ModelImport, 

20 ModelReturn, 

21 Module, 

22 Name, 

23 Operator, 

24 RecordDef, 

25 ScalarType, 

26 StructureType, 

27 SourceRef, 

28 VarType, 

29 Var, 

30 ValueConstructor, 

31) 

32from skema.program_analysis.CAST2FN.ann_cast.annotated_cast import * 

33from skema.program_analysis.CAST2FN.ann_cast.ann_cast_helpers import ( 

34 var_dict_to_str, 

35 interface_to_str, 

36 decision_in_to_str, 

37) 

38 

39 

40class CASTTypeError(TypeError): 

41 """Used to create errors in the CASTToAGraphVisitor, in particular 

42 when the visitor encounters some value that it wasn't expecting. 

43 

44 Args: 

45 Exception: An exception that occurred during execution. 

46 """ 

47 

48 pass 

49 

50 

51class CASTToAGraphVisitor(CASTVisitor): 

52 """class CASTToAGraphVisitor - A visitor that traverses 

53 CAST nodes to generate a networkx DiGraph that represents 

54 the CAST as a DiGraph. The CAST object itself is a representation 

55 of a program. 

56 A common theme across most visitors is they generate a UID 

57 that is used with networkx as identifiers for the nodes in the digraph, 

58 so we know which nodes to connect to other nodes with edges. They then 

59 add themselves to a networkx DiGraph object that is updated across 

60 most the visitors by either adding nodes or edges. 

61 A lot of the visitors are relatively straightforward and 

62 follow this pattern for a particular node 

63 - Visit the node's children 

64 - Generate a UID for the current node 

65 - Add the node to the graph with the UID 

66 - Add edges connecting the node to its children 

67 - Return the Node's UID, so this can be repeated as necessary 

68 

69 Some do a little bit of extra work to make the visualization look 

70 nicer, like add extra 'group' nodes to group certain nodes together 

71 (i.e. function arguments, class attributes) 

72 

73 Inherits from CASTVisitor to use its visit functions. 

74 

75 Attributes: 

76 cast (CAST): The CAST object representation of the program 

77 we're generating a DiGraph for. 

78 G (nx.DiGraph): The graph of the program. Gets populated as 

79 nodes are visited. 

80 

81 """ 

82 

83 cast: CAST 

84 G: nx.DiGraph 

85 

86 def __init__(self, cast: CAST): 

87 self.cast = cast 

88 self.G = nx.DiGraph() 

89 

90 def to_agraph(self): 

91 """Visits the entire CAST object to populate the graph G 

92 and returns an AGraph of the graph G as a result. 

93 """ 

94 if isinstance(self.cast, list): 

95 self.visit_list(self.cast[0].nodes) 

96 else: 

97 self.visit_list(self.cast.nodes) 

98 A = nx.nx_agraph.to_agraph(self.G) 

99 A.graph_attr.update( 

100 {"dpi": 227, "fontsize": 20, "fontname": "Menlo", "rankdir": "TB"} 

101 ) 

102 A.node_attr.update( 

103 { 

104 "shape": "rectangle", 

105 "color": "#650021", 

106 "style": "rounded", 

107 "fontname": "Menlo", 

108 } 

109 ) 

110 for node in A.iternodes(): 

111 node.attr["fontcolor"] = "black" 

112 node.attr["style"] = "rounded" 

113 A.edge_attr.update({"color": "#650021", "arrowsize": 0.5}) 

114 

115 return A 

116 

117 def to_pdf(self, pdf_filepath: str): 

118 """Generates an agraph, and uses it 

119 to create a PDF using the 'dot' program""" 

120 # import skema.utils.misc.test_pygraphviz 

121 # test_pygraphviz( 

122 # "For the agraph generation in the python_to_cast " 

123 # "function to work, pygraphviz must be installed." 

124 # ) 

125 

126 A = self.to_agraph() 

127 A.draw(pdf_filepath, prog="dot") 

128 

129 @singledispatchmethod 

130 def visit(self, node: AstNode): 

131 """Generic visitor for unimplemented/unexpected nodes""" 

132 raise CASTTypeError(f"Unrecognized node type: {type(node)}") 

133 

134 @visit.register 

135 def _(self, node: Assignment): 

136 """Visits Assignment nodes, the node's UID is returned 

137 so it can be used to connect nodes in the digraph""" 

138 left = self.visit(node.left) 

139 right = self.visit(node.right) 

140 node_uid = uuid.uuid4() 

141 self.G.add_node(node_uid, label="Assignment") 

142 self.G.add_edge(node_uid, left) 

143 self.G.add_edge(node_uid, right) 

144 return node_uid 

145 

146 @visit.register 

147 def _(self, node: AnnCastAssignment): 

148 """Visits Assignment nodes, the node's UID is returned 

149 so it can be used to connect nodes in the digraph""" 

150 left = self.visit(node.left) 

151 right = self.visit(node.right) 

152 node_uid = uuid.uuid4() 

153 self.G.add_node(node_uid, label="Assignment") 

154 self.G.add_edge(node_uid, left) 

155 self.G.add_edge(node_uid, right) 

156 return node_uid 

157 

158 @visit.register 

159 def _(self, node: AnnCastAttribute): 

160 """Visits Attribute nodes, the node's UID is returned 

161 so it can be used to connect nodes in the digraph""" 

162 value = self.visit(node.value) 

163 attr = self.visit(node.attr) 

164 node_uid = uuid.uuid4() 

165 self.G.add_node(node_uid, label="Attribute") 

166 self.G.add_edge(node_uid, value) 

167 self.G.add_edge(node_uid, attr) 

168 

169 return node_uid 

170 

171 @visit.register 

172 def _(self, node: Attribute): 

173 """Visits Attribute nodes, the node's UID is returned 

174 so it can be used to connect nodes in the digraph""" 

175 value = self.visit(node.value) 

176 attr = self.visit(node.attr) 

177 node_uid = uuid.uuid4() 

178 self.G.add_node(node_uid, label="Attribute") 

179 self.G.add_edge(node_uid, value) 

180 self.G.add_edge(node_uid, attr) 

181 

182 return node_uid 

183 

184 @visit.register 

185 def _(self, node: Operator): 

186 """Visits BinaryOp nodes, the node's UID is returned 

187 so it can be used to connect nodes in the digraph""" 

188 operands = self.visit_list(node.operands) 

189 node_uid = uuid.uuid4() 

190 self.G.add_node(node_uid, label=node.op) 

191 

192 for opd in operands: 

193 self.G.add_edge(node_uid, opd) 

194 

195 return node_uid 

196 

197 @visit.register 

198 def _(self, node: AnnCastOperator): 

199 """Visits BinaryOp nodes, the node's UID is returned 

200 so it can be used to connect nodes in the digraph""" 

201 operands = self.visit_list(node.operands) 

202 node_uid = uuid.uuid4() 

203 self.G.add_node(node_uid, label=node.op) 

204 

205 for opd in operands: 

206 self.G.add_edge(node_uid, opd) 

207 

208 return node_uid 

209 

210 @visit.register 

211 def _(self, node: Call): 

212 """Visits Call (function call) nodes. We check to see 

213 if we have arguments to the node and act accordingly. 

214 Appending all the arguments of the function to this node, 

215 if we have any. The node's UID is returned.""" 

216 func = self.visit(node.func) 

217 args = [] 

218 

219 if node.arguments != None and len(node.arguments) > 0: 

220 args = self.visit_list(node.arguments) 

221 

222 node_uid = uuid.uuid4() 

223 self.G.add_node(node_uid, label="FunctionCall") 

224 self.G.add_edge(node_uid, func) 

225 

226 args_uid = uuid.uuid4() 

227 self.G.add_node(args_uid, label="Arguments") 

228 self.G.add_edge(node_uid, args_uid) 

229 

230 for n in args: 

231 self.G.add_edge(args_uid, n) 

232 

233 return node_uid 

234 

235 def visit_call_grfn_2_2(self, node: AnnCastCall): 

236 """Visits Call (function call) nodes. We check to see 

237 if we have arguments to the node and act accordingly. 

238 Appending all the arguments of the function to this node, 

239 if we have any. The node's UID is returned.""" 

240 func = self.visit(node.func) 

241 args = [] 

242 if len(node.arguments) > 0: 

243 args = self.visit_list(node.arguments) 

244 

245 node_uid = uuid.uuid4() 

246 label = "CallGrfn2_2" 

247 top_iface_in_vars_str = var_dict_to_str( 

248 "Top In: ", node.top_interface_in 

249 ) 

250 top_iface_out_vars_str = var_dict_to_str( 

251 "Top Out: ", node.top_interface_out 

252 ) 

253 bot_iface_in_vars_str = var_dict_to_str( 

254 "Bot In: ", node.bot_interface_in 

255 ) 

256 bot_iface_out_vars_str = var_dict_to_str( 

257 "Bot Out: ", node.bot_interface_out 

258 ) 

259 globals_in_str = var_dict_to_str( 

260 "Globals In: ", node.func_def_copy.used_globals 

261 ) 

262 globals_out_str = var_dict_to_str( 

263 "Globals Out: ", node.func_def_copy.modified_globals 

264 ) 

265 label = f"{label}\n{top_iface_in_vars_str}\n{top_iface_out_vars_str}" 

266 label = f"{label}\n{bot_iface_in_vars_str}\n{bot_iface_out_vars_str}" 

267 label = f"{label}\n{globals_in_str}\n{globals_out_str}" 

268 self.G.add_node(node_uid, label=label) 

269 self.G.add_edge(node_uid, func) 

270 

271 args_uid = uuid.uuid4() 

272 self.G.add_node(args_uid, label="Arguments") 

273 self.G.add_edge(node_uid, args_uid) 

274 

275 for n in args: 

276 self.G.add_edge(args_uid, n) 

277 

278 return node_uid 

279 

280 @visit.register 

281 def _(self, node: AnnCastCall): 

282 """Visits Call (function call) nodes. We check to see 

283 if we have arguments to the node and act accordingly. 

284 Appending all the arguments of the function to this node, 

285 if we have any. The node's UID is returned.""" 

286 

287 if node.is_grfn_2_2: 

288 return self.visit_call_grfn_2_2(node) 

289 

290 func = self.visit(node.func) 

291 args = [] 

292 if len(node.arguments) > 0: 

293 args = self.visit_list(node.arguments) 

294 

295 node_uid = uuid.uuid4() 

296 label = "Call" 

297 top_iface_in_vars_str = var_dict_to_str( 

298 "Top In: ", node.top_interface_in 

299 ) 

300 top_iface_out_vars_str = var_dict_to_str( 

301 "Top Out: ", node.top_interface_out 

302 ) 

303 bot_iface_in_vars_str = var_dict_to_str( 

304 "Bot In: ", node.bot_interface_in 

305 ) 

306 bot_iface_out_vars_str = var_dict_to_str( 

307 "Bot Out: ", node.bot_interface_out 

308 ) 

309 label = f"{label}\n{top_iface_in_vars_str}\n{top_iface_out_vars_str}" 

310 label = f"{label}\n{bot_iface_in_vars_str}\n{bot_iface_out_vars_str}" 

311 self.G.add_node(node_uid, label=label) 

312 self.G.add_edge(node_uid, func) 

313 

314 args_uid = uuid.uuid4() 

315 self.G.add_node(args_uid, label="Arguments") 

316 self.G.add_edge(node_uid, args_uid) 

317 

318 for n in args: 

319 self.G.add_edge(args_uid, n) 

320 

321 return node_uid 

322 

323 @visit.register 

324 def _(self, node: RecordDef): 

325 """Visits RecordDef nodes. We visit all fields and functions 

326 of the class definition, and connect them to this node. 

327 This node's UID is returned.""" 

328 # TODO: Where should bases field be used? 

329 funcs = [] 

330 fields = [] 

331 bases = [] 

332 if len(node.funcs) > 0: 

333 funcs = self.visit_list(node.funcs) 

334 if len(node.fields) > 0: 

335 fields = self.visit_list(node.fields) 

336 if len(node.bases) > 0: 

337 bases = self.visit_list(node.bases) 

338 node_uid = uuid.uuid4() 

339 self.G.add_node(node_uid, label="Record: " + node.name) 

340 

341 # Add bases to the graph (currently name nodes) 

342 base_uid = uuid.uuid4() 

343 self.G.add_node(base_uid, label="Parent Classes (bases)") 

344 self.G.add_edge(node_uid, base_uid) 

345 for n in bases: 

346 self.G.add_edge(base_uid, n) 

347 

348 # Add attributes to the graph 

349 attr_uid = uuid.uuid4() 

350 self.G.add_node(attr_uid, label="Attributes") 

351 self.G.add_edge(node_uid, attr_uid) 

352 for n in fields: 

353 self.G.add_edge(attr_uid, n) 

354 

355 # Add functions to the graph 

356 funcs_uid = uuid.uuid4() 

357 self.G.add_node(funcs_uid, label="Functions") 

358 self.G.add_edge(node_uid, funcs_uid) 

359 for n in funcs: 

360 self.G.add_edge(funcs_uid, n) 

361 

362 return node_uid 

363 

364 @visit.register 

365 def _(self, node: AnnCastRecordDef): 

366 """Visits RecordDef nodes. We visit all fields and functions 

367 of the class definition, and connect them to this node. 

368 This node's UID is returned.""" 

369 # TODO: Where should bases field be used? 

370 funcs = [] 

371 fields = [] 

372 if len(node.funcs) > 0: 

373 funcs = self.visit_list(node.funcs) 

374 if len(node.fields) > 0: 

375 fields = self.visit_list(node.fields) 

376 node_uid = uuid.uuid4() 

377 self.G.add_node(node_uid, label="Class: " + node.name) 

378 

379 # Add attributes to the graph 

380 attr_uid = uuid.uuid4() 

381 self.G.add_node(attr_uid, label="Attributes") 

382 self.G.add_edge(node_uid, attr_uid) 

383 for n in fields: 

384 self.G.add_edge(attr_uid, n) 

385 

386 # Add functions to the graph 

387 funcs_uid = uuid.uuid4() 

388 self.G.add_node(funcs_uid, label="Functions") 

389 self.G.add_edge(node_uid, funcs_uid) 

390 for n in funcs: 

391 self.G.add_edge(funcs_uid, n) 

392 

393 return node_uid 

394 

395 @visit.register 

396 def _(self, node: AnnCastFunctionDef): 

397 """Visits FunctionDef nodes. We visit all the arguments, and then 

398 we visit the function's statements. They're then added to the graph. 

399 This node's UID is returned.""" 

400 args = [] 

401 body = [] 

402 # print(node.name) 

403 if len(node.func_args) > 0: 

404 args = self.visit_list(node.func_args) 

405 if len(node.body) > 0: 

406 body = self.visit_list(node.body) 

407 

408 node_uid = uuid.uuid4() 

409 args_node = uuid.uuid4() 

410 body_node = uuid.uuid4() 

411 

412 modified_vars_str = var_dict_to_str("Modified: ", node.modified_vars) 

413 vars_accessed_before_mod_str = var_dict_to_str( 

414 "Accessed: ", node.vars_accessed_before_mod 

415 ) 

416 highest_ver = var_dict_to_str("HiVer: ", node.body_highest_var_vers) 

417 globals_in_str = var_dict_to_str("Globals In: ", node.used_globals) 

418 globals_out_str = var_dict_to_str( 

419 "Globals Out: ", node.modified_globals 

420 ) 

421 func_label = f"Function: {node.name}\n{modified_vars_str}\n{vars_accessed_before_mod_str}\n{highest_ver}" 

422 func_label = f"{func_label}\n{globals_in_str}\n{globals_out_str}" 

423 self.G.add_node(node_uid, label=func_label) 

424 self.G.add_node(args_node, label="Arguments") 

425 self.G.add_node(body_node, label="Body") 

426 

427 self.G.add_edge(node_uid, body_node) 

428 self.G.add_edge(node_uid, args_node) 

429 for n in args: 

430 self.G.add_edge(args_node, n) 

431 

432 for n in body: 

433 self.G.add_edge(body_node, n) 

434 

435 return node_uid 

436 

437 @visit.register 

438 def _(self, node: FunctionDef): 

439 """Visits FunctionDef nodes. We visit all the arguments, and then 

440 we visit the function's statements. They're then added to the graph. 

441 This node's UID is returned.""" 

442 args = [] 

443 body = [] 

444 # print(node.name) 

445 if len(node.func_args) > 0: 

446 args = self.visit_list(node.func_args) 

447 if len(node.body) > 0: 

448 body = self.visit_list(node.body) 

449 

450 node_uid = uuid.uuid4() 

451 args_node = uuid.uuid4() 

452 body_node = uuid.uuid4() 

453 

454 # include the Name node's id if we have it 

455 if isinstance(node.name, Name): 

456 label = ( 

457 "Function: " 

458 + str(node.name.name) 

459 + " (id: " 

460 + str(node.name.id) 

461 + ")" 

462 ) 

463 # otherwise name is just str 

464 else: 

465 label = f"Function: {node.name}" 

466 self.G.add_node(node_uid, label=label) 

467 self.G.add_node(args_node, label="Arguments") 

468 self.G.add_node(body_node, label="Body") 

469 

470 self.G.add_edge(node_uid, body_node) 

471 self.G.add_edge(node_uid, args_node) 

472 for n in args: 

473 self.G.add_edge(args_node, n) 

474 

475 for n in body: 

476 self.G.add_edge(body_node, n) 

477 

478 return node_uid 

479 

480 @visit.register 

481 def _(self, node: Goto): 

482 node_uid = uuid.uuid4() 

483 if node.expr == None: 

484 self.G.add_node(node_uid, label=f"Goto {node.label}") 

485 else: 

486 self.G.add_node(node_uid, label="Goto (Computed)") 

487 

488 return node_uid 

489 

490 @visit.register 

491 def _(self, node: Label): 

492 node_uid = uuid.uuid4() 

493 self.G.add_node(node_uid, label=f"Label {node.label}") 

494 return node_uid 

495 

496 @visit.register 

497 def _(self, node: Loop): 

498 """Visits Loop nodes. We visit the conditional expression and the 

499 body of the loop, and connect them to this node in the graph. 

500 This node's UID is returned.""" 

501 expr = self.visit(node.expr) 

502 pre = [] 

503 body = [] 

504 post = [] 

505 

506 if node.pre != None and len(node.pre) > 0: 

507 pre = self.visit_list(node.pre) 

508 

509 if len(node.body) > 0: 

510 body = self.visit_list(node.body) 

511 

512 if node.post != None and len(node.post) > 0: 

513 post = self.visit_list(node.post) 

514 

515 node_uid = uuid.uuid4() 

516 pre_uid = uuid.uuid4() 

517 test_uid = uuid.uuid4() 

518 body_uid = uuid.uuid4() 

519 post_uid = uuid.uuid4() 

520 

521 self.G.add_node(node_uid, label="Loop") 

522 self.G.add_node(pre_uid, label="Pre") 

523 self.G.add_node(test_uid, label="Test") 

524 self.G.add_node(body_uid, label="Body") 

525 self.G.add_node(post_uid, label="Post") 

526 

527 self.G.add_edge(node_uid, pre_uid) 

528 for n in pre: 

529 self.G.add_edge(pre_uid, n) 

530 self.G.add_edge(node_uid, test_uid) 

531 self.G.add_edge(test_uid, expr) 

532 self.G.add_edge(node_uid, body_uid) 

533 self.G.add_edge(node_uid, post_uid) 

534 for n in body: 

535 self.G.add_edge(body_uid, n) 

536 for n in post: 

537 self.G.add_edge(post_uid, n) 

538 

539 return node_uid 

540 

541 @visit.register 

542 def _(self, node: AnnCastLiteralValue): 

543 if node.value_type == ScalarType.INTEGER: 

544 node_uid = uuid.uuid4() 

545 self.G.add_node(node_uid, label=f"Integer: {node.value}") 

546 return node_uid 

547 elif node.value_type == ScalarType.BOOLEAN: 

548 node_uid = uuid.uuid4() 

549 self.G.add_node(node_uid, label=f"Boolean: {str(node.value)}") 

550 return node_uid 

551 elif node.value_type == ScalarType.ABSTRACTFLOAT: 

552 node_uid = uuid.uuid4() 

553 self.G.add_node(node_uid, label=f"abstractFloat: {node.value}") 

554 return node_uid 

555 elif node.value_type == StructureType.LIST: 

556 node_uid = uuid.uuid4() 

557 self.G.add_node(node_uid, label=f"List (str or list): [...]") 

558 return node_uid 

559 elif node.value_type == StructureType.TUPLE: 

560 node_uid = uuid.uuid4() 

561 self.G.add_node(node_uid, label=f"Tuple (...)") 

562 return node_uid 

563 elif node.value_type == StructureType.MAP: 

564 node_uid = uuid.uuid4() 

565 self.G.add_node(node_uid, label="Dict: {...}") 

566 return node_uid 

567 elif node.value_type == "List[Any]": 

568 node_uid = uuid.uuid4() 

569 if isinstance(node.value, ValueConstructor): 

570 op = node.value.operator 

571 init_val = node.value.initial_value.value 

572 if isinstance(node.value.size, CASTLiteralValue): 

573 size = node.value.size.value 

574 id = -1 

575 else: 

576 size = node.value.size.name 

577 id = node.value.size.id 

578 

579 # self.G.add_node(node_uid, label=f"List: Init_Val: [{init_val}], Size: {size} ") 

580 self.G.add_node( 

581 node_uid, 

582 label=f"List: [{init_val}] {op} {size} (id: {id})", 

583 ) 

584 return node_uid 

585 elif node.value_type == StructureType.MAP: 

586 node_uid = uuid.uuid4() 

587 self.G.add_node(node_uid, label=f"Dict: {node.value}") 

588 return node_uid 

589 else: 

590 assert ( 

591 False 

592 ), f"cast_to_agraph_visitor LiteralValue: type not supported yet {type(node)}" 

593 

594 @visit.register 

595 def _(self, node: CASTLiteralValue): 

596 if node.value_type == ScalarType.INTEGER: 

597 node_uid = uuid.uuid4() 

598 self.G.add_node(node_uid, label=f"Integer: {node.value}") 

599 return node_uid 

600 elif node.value_type == ScalarType.CHARACTER: 

601 node_uid = uuid.uuid4() 

602 self.G.add_node(node_uid, label=f"Character: {str(node.value)}") 

603 return node_uid 

604 elif node.value_type == ScalarType.BOOLEAN: 

605 node_uid = uuid.uuid4() 

606 self.G.add_node(node_uid, label=f"Boolean: {str(node.value)}") 

607 return node_uid 

608 elif node.value_type == ScalarType.ABSTRACTFLOAT: 

609 node_uid = uuid.uuid4() 

610 self.G.add_node(node_uid, label=f"abstractFloat: {node.value}") 

611 return node_uid 

612 elif node.value_type == StructureType.LIST: 

613 node_uid = uuid.uuid4() 

614 self.G.add_node(node_uid, label=f"List (str or list): [...]") 

615 return node_uid 

616 elif node.value_type == StructureType.TUPLE: 

617 node_uid = uuid.uuid4() 

618 self.G.add_node(node_uid, label=f"Tuple (...)") 

619 return node_uid 

620 elif node.value_type == None: 

621 node_uid = uuid.uuid4() 

622 self.G.add_node(node_uid, label=f"None") 

623 return node_uid 

624 elif node.value_type == "List[Any]": 

625 node_uid = uuid.uuid4() 

626 if isinstance(node.value, ValueConstructor): 

627 op = node.value.operator 

628 init_val = node.value.initial_value.value 

629 if isinstance(node.value.size, CASTLiteralValue): 

630 size = node.value.size.value 

631 id = -1 

632 else: 

633 size = node.value.size.name 

634 id = node.value.size.id 

635 

636 # self.G.add_node(node_uid, label=f"List: Init_Val: [{init_val}], Size: {size} ") 

637 self.G.add_node( 

638 node_uid, 

639 label=f"List: [{init_val}] {op} {size} (id: {id})", 

640 ) 

641 elif node.value_type == StructureType.MAP: 

642 node_uid = uuid.uuid4() 

643 self.G.add_node(node_uid, label=f"Dict: {node.value}") 

644 return node_uid 

645 else: 

646 print(node) 

647 assert ( 

648 False 

649 ), f"cast_to_agraph_visitor LiteralValue: type not supported yet {type(node)}" 

650 

651 @visit.register 

652 def _(self, node: AnnCastLoop): 

653 """Visits Loop nodes. We visit the initial statements, the conditional expression and the 

654 body of the loop, and connect them to this node in the graph. 

655 This node's UID is returned.""" 

656 expr = self.visit(node.expr) 

657 init = [] 

658 body = [] 

659 if len(node.init) > 0: 

660 init = self.visit_list(node.init) 

661 if len(node.body) > 0: 

662 body = self.visit_list(node.body) 

663 node_uid = uuid.uuid4() 

664 init_uid = uuid.uuid4() 

665 test_uid = uuid.uuid4() 

666 body_uid = uuid.uuid4() 

667 

668 modified_vars = var_dict_to_str("Modified: ", node.modified_vars) 

669 vars_accessed_before_mod = var_dict_to_str( 

670 "Accessed: ", node.vars_accessed_before_mod 

671 ) 

672 top_iface_init_vars = interface_to_str( 

673 "Top Init: ", node.top_interface_initial 

674 ) 

675 top_iface_updt_vars = interface_to_str( 

676 "Top Updt: ", node.top_interface_updated 

677 ) 

678 top_iface_out_vars = interface_to_str( 

679 "Top Out: ", node.top_interface_out 

680 ) 

681 bot_iface_in_vars = interface_to_str("Bot In: ", node.bot_interface_in) 

682 bot_iface_out_vars = interface_to_str( 

683 "Bot Out: ", node.bot_interface_out 

684 ) 

685 loop_label = f"Loop\n{modified_vars}\n{vars_accessed_before_mod}" 

686 loop_label = f"{loop_label}\n{top_iface_init_vars}\n{top_iface_updt_vars}\n{top_iface_out_vars}" 

687 loop_label = f"{loop_label}\n{bot_iface_in_vars}\n{bot_iface_out_vars}" 

688 self.G.add_node(node_uid, label=loop_label) 

689 self.G.add_node(init_uid, label="Init") 

690 self.G.add_node(test_uid, label="Test") 

691 self.G.add_node(body_uid, label="Body") 

692 

693 self.G.add_edge(node_uid, init_uid) 

694 for n in init: 

695 self.G.add_edge(init_uid, n) 

696 

697 self.G.add_edge(node_uid, test_uid) 

698 self.G.add_edge(test_uid, expr) 

699 self.G.add_edge(node_uid, body_uid) 

700 for n in body: 

701 self.G.add_edge(body_uid, n) 

702 

703 return node_uid 

704 

705 @visit.register 

706 def _(self, node: ModelBreak): 

707 """Visits a ModelBreak (break statment) node. 

708 The node's UID is returned""" 

709 node_uid = uuid.uuid4() 

710 self.G.add_node(node_uid, label="Break") 

711 return node_uid 

712 

713 @visit.register 

714 def _(self, node: ModelContinue): 

715 """Visits a ModelContinue (continue statment) node. 

716 The node's UID is returned""" 

717 node_uid = uuid.uuid4() 

718 self.G.add_node(node_uid, label="Continue") 

719 return node_uid 

720 

721 @visit.register 

722 def _(self, node: ModelIf): 

723 """Visits a ModelIf (If statement) node. 

724 We visit the condition, and then the body and orelse 

725 attributes if we have any. They're all added to the Graph 

726 accordingly. The node's UID is returned.""" 

727 expr = self.visit(node.expr) 

728 body = [] 

729 orelse = [] 

730 if len(node.body) > 0: 

731 body = self.visit_list(node.body) 

732 if len(node.orelse) > 0: 

733 orelse = self.visit_list(node.orelse) 

734 

735 node_uid = uuid.uuid4() 

736 test_uid = uuid.uuid4() 

737 self.G.add_node(node_uid, label="If") 

738 self.G.add_node(test_uid, label="Test") 

739 self.G.add_edge(node_uid, test_uid) 

740 self.G.add_edge(test_uid, expr) 

741 

742 body_uid = uuid.uuid4() 

743 orelse_uid = uuid.uuid4() 

744 

745 # TODO: Handle strings of If/Elif/Elif/... constructs 

746 self.G.add_node(body_uid, label="If Body") 

747 self.G.add_node(orelse_uid, label="Else Body") 

748 

749 self.G.add_edge(node_uid, body_uid) 

750 self.G.add_edge(node_uid, orelse_uid) 

751 

752 for n in body: 

753 self.G.add_edge(body_uid, n) 

754 

755 for n in orelse: 

756 self.G.add_edge(orelse_uid, n) 

757 

758 return node_uid 

759 

760 @visit.register 

761 def _(self, node: AnnCastModelImport): 

762 """Visits a ModelImport (Import statement) node. 

763 name, alias, symbol, all 

764 

765 The node's UID is returned.""" 

766 

767 node_uid = uuid.uuid4() 

768 

769 # TODO: Handle strings of If/Elif/Elif/... constructs 

770 self.G.add_node( 

771 node_uid, 

772 label=f"Import {node.name}\nAlias: {node.alias}\nSymbol: {node.symbol}\nAll: {node.all}", 

773 ) 

774 

775 return node_uid 

776 

777 @visit.register 

778 def _(self, node: ModelImport): 

779 """Visits a ModelImport (Import statement) node. 

780 name, alias, symbol, all 

781 

782 The node's UID is returned.""" 

783 

784 node_uid = uuid.uuid4() 

785 

786 # TODO: Handle strings of If/Elif/Elif/... constructs 

787 self.G.add_node( 

788 node_uid, 

789 label=f"Import {node.name}\nAlias: {node.alias}\nSymbol: {node.symbol}\nAll: {node.all}", 

790 ) 

791 

792 return node_uid 

793 

794 @visit.register 

795 def _(self, node: AnnCastModelIf): 

796 """Visits a ModelIf (If statement) node. 

797 We visit the condition, and then the body and orelse 

798 attributes if we have any. They're all added to the Graph 

799 accordingly. The node's UID is returned.""" 

800 expr = self.visit(node.expr) 

801 body = [] 

802 orelse = [] 

803 if len(node.body) > 0: 

804 body = self.visit_list(node.body) 

805 if len(node.orelse) > 0: 

806 orelse = self.visit_list(node.orelse) 

807 

808 node_uid = uuid.uuid4() 

809 test_uid = uuid.uuid4() 

810 

811 modified_vars_str = var_dict_to_str("Modified: ", node.modified_vars) 

812 vars_accessed_before_mod_str = var_dict_to_str( 

813 "Accessed: ", node.vars_accessed_before_mod 

814 ) 

815 # top inteface 

816 top_iface_in_vars_str = interface_to_str( 

817 "Top In: ", node.top_interface_in 

818 ) 

819 top_iface_out_vars_str = interface_to_str( 

820 "Top Out: ", node.top_interface_out 

821 ) 

822 # condition node 

823 condition_in = interface_to_str("Cond In: ", node.condition_in) 

824 condition_out = interface_to_str("Cond Out: ", node.condition_out) 

825 # decision node 

826 decision_in = decision_in_to_str("Dec In: ", node.decision_in) 

827 decision_out = interface_to_str("Dec Out: ", node.decision_out) 

828 # bot interface 

829 bot_iface_in_vars_str = interface_to_str( 

830 "Bot In: ", node.bot_interface_in 

831 ) 

832 bot_iface_out_vars_str = interface_to_str( 

833 "Bot Out: ", node.bot_interface_out 

834 ) 

835 if_label = f"If\n{modified_vars_str}\n{vars_accessed_before_mod_str}" 

836 if_label = ( 

837 f"{if_label}\n{top_iface_in_vars_str}\n{top_iface_out_vars_str}" 

838 ) 

839 if_label = f"{if_label}\n{condition_in}\n{condition_out}" 

840 if_label = f"{if_label}\n{decision_in}\n{decision_out}" 

841 if_label = ( 

842 f"{if_label}\n{bot_iface_in_vars_str}\n{bot_iface_out_vars_str}" 

843 ) 

844 self.G.add_node(node_uid, label=if_label) 

845 self.G.add_node(test_uid, label="Test") 

846 self.G.add_edge(node_uid, test_uid) 

847 self.G.add_edge(test_uid, expr) 

848 

849 body_uid = uuid.uuid4() 

850 orelse_uid = uuid.uuid4() 

851 

852 # TODO: Handle strings of If/Elif/Elif/... constructs 

853 self.G.add_node(body_uid, label="If Body") 

854 self.G.add_node(orelse_uid, label="Else Body") 

855 

856 self.G.add_edge(node_uid, body_uid) 

857 self.G.add_edge(node_uid, orelse_uid) 

858 

859 for n in body: 

860 self.G.add_edge(body_uid, n) 

861 

862 for n in orelse: 

863 self.G.add_edge(orelse_uid, n) 

864 

865 return node_uid 

866 

867 @visit.register 

868 def _(self, node: AnnCastModelReturn): 

869 """Visits a ModelReturn (return statment) node. 

870 We add the return value to this node with an edge. 

871 The node's UID is returned""" 

872 value = self.visit(node.value) 

873 node_uid = uuid.uuid4() 

874 self.G.add_node(node_uid, label="Return") 

875 self.G.add_edge(node_uid, value) 

876 

877 return node_uid 

878 

879 @visit.register 

880 def _(self, node: ModelReturn): 

881 """Visits a ModelReturn (return statment) node. 

882 We add the return value to this node with an edge. 

883 The node's UID is returned""" 

884 value = self.visit(node.value) 

885 node_uid = uuid.uuid4() 

886 self.G.add_node(node_uid, label="Return") 

887 self.G.add_edge(node_uid, value) 

888 

889 return node_uid 

890 

891 @visit.register 

892 def _(self, node: AnnCastModule): 

893 """Visits a Module node. This is the starting point for visiting 

894 a CAST object. The return value isn't relevant here (I think)""" 

895 program_uuid = uuid.uuid4() 

896 self.G.add_node(program_uuid, label="Program: " + node.name) 

897 

898 module_uuid = uuid.uuid4() 

899 modified_vars_str = var_dict_to_str("Modified: ", node.modified_vars) 

900 vars_accessed_before_mod_str = var_dict_to_str( 

901 "Accessed: ", node.vars_accessed_before_mod 

902 ) 

903 used_vars_str = var_dict_to_str("Used: ", node.used_vars) 

904 module_label = f"Module: {node.name}\n{modified_vars_str}\n{vars_accessed_before_mod_str}" 

905 module_label = f"{module_label}\n{used_vars_str}" 

906 self.G.add_node(module_uuid, label=module_label) 

907 self.G.add_edge(program_uuid, module_uuid) 

908 

909 body = self.visit_list(node.body) 

910 for b in body: 

911 self.G.add_edge(module_uuid, b) 

912 

913 return program_uuid 

914 

915 @visit.register 

916 def _(self, node: Module): 

917 """Visits a Module node. This is the starting point for visiting 

918 a CAST object. The return value isn't relevant here (I think)""" 

919 program_uuid = uuid.uuid4() 

920 

921 if node.name != None: 

922 self.G.add_node(program_uuid, label="Program: " + node.name) 

923 else: 

924 self.G.add_node(program_uuid, label="Program") 

925 

926 

927 module_uuid = uuid.uuid4() 

928 if node.name != None: 

929 self.G.add_node(module_uuid, label="Module: " + node.name) 

930 else: 

931 self.G.add_node(module_uuid, label="Module") 

932 

933 self.G.add_edge(program_uuid, module_uuid) 

934 

935 body = self.visit_list(node.body) 

936 for b in body: 

937 self.G.add_edge(module_uuid, b) 

938 

939 return program_uuid 

940 

941 @visit.register 

942 def _(self, node: AnnCastName): 

943 """Visits a Name node. We check to see if this Name node 

944 belongs to a class. In which case it's being called as an 

945 init(), and add this node's name to the graph accordingly, 

946 and return the UID of this node.""" 

947 node_uid = uuid.uuid4() 

948 

949 class_init = False 

950 for n in self.cast.nodes[0].body: 

951 if isinstance(n, RecordDef) and n.name == node.name: 

952 class_init = True 

953 self.G.add_node(node_uid, label=node.name + " Init()") 

954 break 

955 

956 if not class_init: 

957 if isinstance(node.con_scope, list): 

958 label = node.name + "\n" + ".".join(node.con_scope) 

959 else: 

960 label = node.name 

961 label += f"\nver: {str(node.version)}, id: {str(node.id)}" 

962 

963 self.G.add_node(node_uid, label=label) 

964 

965 return node_uid 

966 

967 @visit.register 

968 def _(self, node: Name): 

969 """Visits a Name node. We check to see if this Name node 

970 belongs to a class. In which case it's being called as an 

971 init(), and add this node's name to the graph accordingly, 

972 and return the UID of this node.""" 

973 node_uid = uuid.uuid4() 

974 

975 class_init = False 

976 body = self.cast[0].nodes[0].body if isinstance(self.cast, list) else self.cast.nodes[0].body 

977 for n in body: 

978 if isinstance(n, RecordDef) and n.name == node.name: 

979 class_init = True 

980 self.G.add_node(node_uid, label=node.name + " Init()") 

981 break 

982 

983 if not class_init: 

984 if node.name == None: 

985 self.G.add_node( 

986 node_uid, label="NONAME (id: " + str(node.id) + ")" 

987 ) 

988 else: 

989 self.G.add_node( 

990 node_uid, label=node.name + " (id: " + str(node.id) + ")" 

991 ) 

992 

993 return node_uid 

994 

995 @visit.register 

996 def _(self, node: AnnCastVar): 

997 """Visits a Var node by visiting its value""" 

998 val = self.visit(node.val) 

999 return val 

1000 

1001 @visit.register 

1002 def _(self, node: Var): 

1003 """Visits a Var node by visiting its value""" 

1004 if node.default_value == None: 

1005 val = self.visit(node.val) 

1006 return val 

1007 else: 

1008 val = self.visit(node.default_value) 

1009 node_uid = uuid.uuid4() 

1010 self.G.add_node( 

1011 node_uid, label=f"{node.val.name} (id: {str(node.val.id)})" 

1012 ) # value: {node.default_value.value}") 

1013 self.G.add_edge(node_uid, val) 

1014 return node_uid