New project structure

This commit is contained in:
Jan-Niclas Loosen
2026-03-03 16:45:00 +01:00
parent 9c18e5e044
commit 8a40cb2636
98 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,117 @@
from typing import Any, Callable, Set
from .CFG_Node import *
class CFG:
def __init__(self, ast):
start = CFG_START()
start.dot_style = 'style=filled, color=gray'
end = CFG_END()
end.dot_style = 'style=filled, color=gray'
last = ast.cfa(start, end)
if last is not None:
last.add_child(end)
self.START = start
self.END = end
self.ast = ast
# Remove empty nodes and rewire edges
all_nodes = self.nodes()
nodes_to_remove = [node for node in all_nodes if node.is_empty()]
for node in nodes_to_remove:
self.__remove_and_rewire(node)
def nodes(self):
all_nodes = set()
self.__collect_nodes(self.START, all_nodes)
return all_nodes
def __collect_nodes(self, node, node_set):
if node in node_set:
return
node_set.add(node)
for child in node.children:
self.__collect_nodes(child, node_set)
def __remove_and_rewire(self, node):
original_children = list(node.children)
for parent in list(node.parents):
if node in parent.children:
# For diamond nodes, preserve the true and false bodies
if isinstance(node, CFG_DIAMOND):
targets = []
if len(original_children) >= 1:
true_target = self.__first_filled_child(original_children[0])
if true_target:
targets.append(true_target)
if len(original_children) >= 2:
false_target = self.__first_filled_child(original_children[1])
if false_target:
targets.append(false_target)
# For regular nodes, find all non-empty targets
else:
targets = []
for child in original_children:
target = self.__first_filled_child(child)
if target and target not in targets:
targets.append(target)
# Remove edge from parent to node
parent.remove_child(node, propagate=False)
# Add edges from parent to targets
for target in targets:
parent.add_child(target, propagate=False)
# Clear the node's connections
node.parents.clear()
node.children.clear()
def __first_filled_child(self, node):
if not node.is_empty():
return node
# Recursively check children
for child in sorted(node.children, key=lambda n: n.id):
result = self.__first_filled_child(child)
if result is not None:
return result
return None
def to_dot(self) -> str:
lines = ["digraph CFG {", ' node [fontname="Helvetica"];']
def emit(node: CFG_Node):
label = node.dot_label()
shape = node.dot_shape
style = node.dot_style
style_str = f", {style}" if style else ""
lines.append(f' n{node.id} [label="{label}", shape={shape}{style_str}];')
for i, child in enumerate(sorted(node.children, key=lambda n: n.id)):
edge_label = ""
if isinstance(node, CFG_DIAMOND):
if i == 0:
edge_label = ' [label="T"]'
elif i == 1:
edge_label = ' [label="F"]'
lines.append(f" n{node.id} -> n{child.id}{edge_label};")
self.traverse(emit, start=self.START)
lines.append("}")
return "\n".join(lines)
# Reusable traversal function
def traverse(self, fn: Callable[[CFG_Node], Any], start: CFG_Node | None = None) -> None:
start = start or self.START
visited: Set[int] = set()
def visit(node: CFG_Node):
if node.id in visited:
return
visited.add(node.id)
fn(node)
for child in sorted(node.children, key=lambda n: n.id):
visit(child)
visit(start)

View File

@@ -0,0 +1,96 @@
class CFG_Node:
__counter = 1
def __init__(self, ast_node=None):
self.ast_node = ast_node
self.children = set()
self.parents = set()
self.label = None
self.dot_shape = 'box'
self.dot_style = ''
self.id = CFG_Node.__counter
CFG_Node.__counter += 1
def get_children(self):
return self.children
def get_parents(self):
return self.parents
def add_child(self, child: 'CFG_Node', propagate=True):
if propagate:
child.parents.add(self)
self.children.add(child)
def add_parent(self, parent: 'CFG_Node', propagate=True):
if propagate:
parent.add_child(self)
self.parents.add(parent)
def remove_child(self, child: 'CFG_Node', propagate=True):
if propagate:
child.parents.remove(self)
self.children.remove(child)
def remove_parent(self, parent: 'CFG_Node', propagate=True):
if propagate:
parent.children.remove(self)
self.parents.remove(parent)
def dot_label(self):
# Prioritize custom label
if self.label is not None:
return self.label
# Build label from AST node
if self.ast_node is not None:
return str(self.ast_node)
return None
def is_filled(self):
return not self.is_empty()
def is_empty(self):
# Node is empty if it has no label and no related AST node
if self.label is None or self.label == "None":
if self.ast_node is not None:
# Node belongs to a ast node
return False
return True
# Node is required for the control flow
return False
class CFG_START(CFG_Node):
def __init__(self, ast_node=None):
super().__init__(ast_node)
self.dot_shape = "ellipse"
self.dot_style = 'style=filled, color=green'
self.label = "START"
class CFG_END(CFG_Node):
def __init__(self, ast_node=None):
super().__init__(ast_node)
self.dot_shape = "ellipse"
self.dot_style = 'style=filled, color=green'
self.label = "END"
class CFG_DIAMOND(CFG_Node):
def __init__(self, ast_node=None):
super().__init__(ast_node)
self.dot_shape = "diamond"
self.label = "<?>"
class CFG_CALL(CFG_Node):
def __init__(self, ast_node=None):
super().__init__(ast_node)
self.dot_style = 'style=filled, color=orange'
self.dot_shape = "box"
class CFG_RETURN(CFG_Node):
def __init__(self, ast_node=None):
super().__init__(ast_node)
self.dot_style = 'style=filled, color=orange'
self.dot_shape = "box"

View File