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
« prev ^ index » next coverage.py v7.5.0, created at 2024-04-30 17:15 +0000
1import networkx as nx
3from functools import singledispatchmethod
4from skema.utils.misc import uuid
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)
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.
44 Args:
45 Exception: An exception that occurred during execution.
46 """
48 pass
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
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)
73 Inherits from CASTVisitor to use its visit functions.
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.
81 """
83 cast: CAST
84 G: nx.DiGraph
86 def __init__(self, cast: CAST):
87 self.cast = cast
88 self.G = nx.DiGraph()
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})
115 return A
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 # )
126 A = self.to_agraph()
127 A.draw(pdf_filepath, prog="dot")
129 @singledispatchmethod
130 def visit(self, node: AstNode):
131 """Generic visitor for unimplemented/unexpected nodes"""
132 raise CASTTypeError(f"Unrecognized node type: {type(node)}")
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
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
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)
169 return node_uid
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)
182 return node_uid
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)
192 for opd in operands:
193 self.G.add_edge(node_uid, opd)
195 return node_uid
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)
205 for opd in operands:
206 self.G.add_edge(node_uid, opd)
208 return node_uid
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 = []
219 if node.arguments != None and len(node.arguments) > 0:
220 args = self.visit_list(node.arguments)
222 node_uid = uuid.uuid4()
223 self.G.add_node(node_uid, label="FunctionCall")
224 self.G.add_edge(node_uid, func)
226 args_uid = uuid.uuid4()
227 self.G.add_node(args_uid, label="Arguments")
228 self.G.add_edge(node_uid, args_uid)
230 for n in args:
231 self.G.add_edge(args_uid, n)
233 return node_uid
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)
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)
271 args_uid = uuid.uuid4()
272 self.G.add_node(args_uid, label="Arguments")
273 self.G.add_edge(node_uid, args_uid)
275 for n in args:
276 self.G.add_edge(args_uid, n)
278 return node_uid
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."""
287 if node.is_grfn_2_2:
288 return self.visit_call_grfn_2_2(node)
290 func = self.visit(node.func)
291 args = []
292 if len(node.arguments) > 0:
293 args = self.visit_list(node.arguments)
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)
314 args_uid = uuid.uuid4()
315 self.G.add_node(args_uid, label="Arguments")
316 self.G.add_edge(node_uid, args_uid)
318 for n in args:
319 self.G.add_edge(args_uid, n)
321 return node_uid
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)
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)
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)
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)
362 return node_uid
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)
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)
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)
393 return node_uid
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)
408 node_uid = uuid.uuid4()
409 args_node = uuid.uuid4()
410 body_node = uuid.uuid4()
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")
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)
432 for n in body:
433 self.G.add_edge(body_node, n)
435 return node_uid
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)
450 node_uid = uuid.uuid4()
451 args_node = uuid.uuid4()
452 body_node = uuid.uuid4()
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")
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)
475 for n in body:
476 self.G.add_edge(body_node, n)
478 return node_uid
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)")
488 return node_uid
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
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 = []
506 if node.pre != None and len(node.pre) > 0:
507 pre = self.visit_list(node.pre)
509 if len(node.body) > 0:
510 body = self.visit_list(node.body)
512 if node.post != None and len(node.post) > 0:
513 post = self.visit_list(node.post)
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()
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")
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)
539 return node_uid
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
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)}"
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
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)}"
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()
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")
693 self.G.add_edge(node_uid, init_uid)
694 for n in init:
695 self.G.add_edge(init_uid, n)
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)
703 return node_uid
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
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
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)
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)
742 body_uid = uuid.uuid4()
743 orelse_uid = uuid.uuid4()
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")
749 self.G.add_edge(node_uid, body_uid)
750 self.G.add_edge(node_uid, orelse_uid)
752 for n in body:
753 self.G.add_edge(body_uid, n)
755 for n in orelse:
756 self.G.add_edge(orelse_uid, n)
758 return node_uid
760 @visit.register
761 def _(self, node: AnnCastModelImport):
762 """Visits a ModelImport (Import statement) node.
763 name, alias, symbol, all
765 The node's UID is returned."""
767 node_uid = uuid.uuid4()
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 )
775 return node_uid
777 @visit.register
778 def _(self, node: ModelImport):
779 """Visits a ModelImport (Import statement) node.
780 name, alias, symbol, all
782 The node's UID is returned."""
784 node_uid = uuid.uuid4()
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 )
792 return node_uid
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)
808 node_uid = uuid.uuid4()
809 test_uid = uuid.uuid4()
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)
849 body_uid = uuid.uuid4()
850 orelse_uid = uuid.uuid4()
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")
856 self.G.add_edge(node_uid, body_uid)
857 self.G.add_edge(node_uid, orelse_uid)
859 for n in body:
860 self.G.add_edge(body_uid, n)
862 for n in orelse:
863 self.G.add_edge(orelse_uid, n)
865 return node_uid
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)
877 return node_uid
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)
889 return node_uid
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)
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)
909 body = self.visit_list(node.body)
910 for b in body:
911 self.G.add_edge(module_uuid, b)
913 return program_uuid
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()
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")
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")
933 self.G.add_edge(program_uuid, module_uuid)
935 body = self.visit_list(node.body)
936 for b in body:
937 self.G.add_edge(module_uuid, b)
939 return program_uuid
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()
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
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)}"
963 self.G.add_node(node_uid, label=label)
965 return node_uid
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()
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
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 )
993 return node_uid
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
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