diff --git a/Project-02-03-04/Source.gv.png b/Project-02-03-04/Source.gv.png index 1b035f4..54832a6 100644 Binary files a/Project-02-03-04/Source.gv.png and b/Project-02-03-04/Source.gv.png differ diff --git a/Project-02-03-04/cfg/CFG.py b/Project-02-03-04/cfg/CFG.py index bf52cc8..71e9b17 100644 --- a/Project-02-03-04/cfg/CFG.py +++ b/Project-02-03-04/cfg/CFG.py @@ -1,3 +1,5 @@ +from typing import Any + from .CFG_Node import * @@ -8,13 +10,11 @@ class CFG: def to_dot(self) -> str: visited = set() - visited_nodes = [] # Track all visited nodes for special edge handling + visited_nodes = [] lines = ["digraph CFG {"] - - # optionale Defaults lines.append(' node [fontname="Helvetica"];') - def node_label(node: CFG_Node) -> str: + def node_label(node: CFG_Node) -> str | None | Any: # Skip empty nodes (nodes with no meaningful content) if hasattr(node, 'label') and node.label == "None": return None @@ -38,10 +38,10 @@ class CFG: else: return node.label - # Basislabel aus dem Knoten + # Base label from the node base = node.dot_label() if hasattr(node, "dot_label") else "" - # semantisches Label aus AST + # Semantic label from AST if node.ast_node is not None: semantic = str(node.ast_node) label_content = f"{base}\n{semantic}" if base else semantic @@ -65,7 +65,6 @@ class CFG: return ', '.join(styles) if styles else '' def find_first_non_empty_child(node: CFG_Node): - """Find the first descendant of a node that has a non-empty label""" if node_label(node) is not None: return node @@ -83,6 +82,7 @@ class CFG: label = node_label(node) visited_nodes.append(node) # Track all visited nodes + # Skip nodes that should not be included in the output if label is None: visited.add(node.id) @@ -140,11 +140,36 @@ class CFG: visit(target) continue + # Special handling for RETURN nodes that connect to empty cont nodes + # This is especially important for recursive function calls + if (label and (label.startswith("RET ") or label.startswith("CALL ")) and + child_label is None and len(child.children) > 0): + # This is a RETURN/CALL node connecting to an empty cont node + # Recursively find all non-empty targets that the cont node connects to + def find_all_targets(n): + """Recursively find all non-empty targets""" + targets = [] + if node_label(n) is not None: + targets.append(n) + else: + for grandchild in sorted(n.children, key=lambda n: n.id): + targets.extend(find_all_targets(grandchild)) + return targets + + cont_targets = find_all_targets(child) + + # Connect the RETURN/CALL node directly to the cont node's targets + if cont_targets: + for target in cont_targets: + lines.append(f" n{node.id} -> n{target.id};") + visit(target) + continue + # Visit the child but don't create an edge visit(child) continue - - # Add edge labels for diamond nodes + + # Add edge labels for diamond nodes (conditional branches) edge_label = "" if hasattr(node, 'dot_shape') and node.dot_shape() == "diamond": if i == 0: @@ -156,7 +181,7 @@ class CFG: visit(child) # Add special edges for recursive calls in function g - # RET g(y) should connect to the FINAL x that leads to function end + # This handles the specific case where RET g(y) should connect to the x variable if label and label.startswith("RET g(y)"): # Find the FINAL x variable node that leads to function end final_x_node = None @@ -175,6 +200,7 @@ class CFG: if final_x_node: lines.append(f" n{node.id} -> n{final_x_node.id};") + # Start the CFG traversal from the entry node visit(self.in_node) lines.append("}") return "\n".join(lines) \ No newline at end of file diff --git a/Project-02-03-04/cfg/CFG_Node.py b/Project-02-03-04/cfg/CFG_Node.py index ef017d2..0a9fa81 100644 --- a/Project-02-03-04/cfg/CFG_Node.py +++ b/Project-02-03-04/cfg/CFG_Node.py @@ -1,11 +1,12 @@ class CFG_Node: __counter = 1 - def __init__(self, ast_node = None): + def __init__(self, ast_node=None): self.ast_node = ast_node self.children = set() self.parents = set() - + self.label = None # Optional label for the node + self.id = CFG_Node.__counter CFG_Node.__counter += 1 @@ -15,26 +16,37 @@ class CFG_Node: def get_parents(self): return self.parents - def add_child(self, child: CFG_Node, propagate = True): + 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): + 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): + 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): + def remove_parent(self, parent: 'CFG_Node', propagate=True): if propagate: parent.children.remove(self) self.parents.remove(parent) + def __str__(self): + if self.label: + return f"CFG_Node({self.id}, label='{self.label}')" + elif self.ast_node: + return f"CFG_Node({self.id}, ast={type(self.ast_node).__name__})" + else: + return f"CFG_Node({self.id})" + + def __repr__(self): + return self.__str__() + class CFG_START(CFG_Node): def dot_shape(self): diff --git a/Project-02-03-04/cfg_build.py b/Project-02-03-04/cfg_build.py index 220669c..592acfe 100644 --- a/Project-02-03-04/cfg_build.py +++ b/Project-02-03-04/cfg_build.py @@ -10,6 +10,7 @@ from cfg.CFG_Node import ( import compiler import syntax +# Global registry for function start/end nodes FUNCTIONS = {} class CONST(compiler.CONST): @@ -28,9 +29,13 @@ class ID(compiler.ID): class AOP(compiler.AOP): def cfa(self, pred, end): + # Create nodes for each operand separately (like the example) left_node = self.arg1.cfa(pred, None) right_node = self.arg2.cfa(left_node, None) + + # Create the comparison node with just the operator op_node = CFG_Node(self) + op_node.label = f"{self.operator}" right_node.add_child(op_node) op_node.add_child(end) if end else None return op_node @@ -41,28 +46,38 @@ class COMP(compiler.COMP): left_node = self.arg1.cfa(pred, None) right_node = self.arg2.cfa(left_node, None) - # Create the comparison node with the full expression + # Create the comparison node with just the operator comp_node = CFG_Node(self) - comp_node.label = f"({str(self.arg1)} {self.operator} {str(self.arg2)})" + comp_node.label = f"{self.operator}" right_node.add_child(comp_node) comp_node.add_child(end) if end else None return comp_node class EQOP(compiler.EQOP): def cfa(self, pred, end): + # Create nodes for each operand separately (like the example) left_node = self.arg1.cfa(pred, None) right_node = self.arg2.cfa(left_node, None) + + # Create the equation node with just the operator eqop_node = CFG_Node(self) + eqop_node.label = f"{self.operator}" right_node.add_child(eqop_node) eqop_node.add_child(end) if end else None return eqop_node class LOP(compiler.LOP): def cfa(self, pred, end): - n = CFG_Node(self) - pred.add_child(n) - n.add_child(end) if end else None - return n + # Create nodes for each operand separately + left_node = self.arg1.cfa(pred, None) + right_node = self.arg2.cfa(left_node, None) + + # Create the logical operation node with just the operator + lop_node = CFG_Node(self) + lop_node.label = f"{self.operator}" + right_node.add_child(lop_node) + lop_node.add_child(end) if end else None + return lop_node class ASSIGN(compiler.ASSIGN): def cfa(self, pred, end): @@ -83,7 +98,7 @@ class IF(compiler.IF): def cfa(self, pred, end): cond_node = self.cond.cfa(pred, None) diamond = CFG_DIAMOND(self.cond) - diamond.label = "<>" # Use simple diamond label + diamond.label = "" # Use simple diamond label cond_node.add_child(diamond) then_entry = CFG_Node() else_entry = CFG_Node() @@ -97,15 +112,19 @@ class IF(compiler.IF): class WHILE(compiler.WHILE): def cfa(self, pred, end): - # Create the condition evaluation nodes - # First, create the left operand node - left_node = self.cond.arg1.cfa(pred, None) - # Then create the right operand node - right_node = self.cond.arg2.cfa(left_node, None) - # Then create the comparison node - comp_node = CFG_Node(self.cond) - comp_node.label = f"({str(self.cond.arg1)} {self.cond.operator} {str(self.cond.arg2)})" - right_node.add_child(comp_node) + # Handle different types of conditions + if hasattr(self.cond, 'arg1') and hasattr(self.cond, 'arg2'): + # This is a comparison operation (e.g., a > b) + # Create the condition evaluation nodes + left_node = self.cond.arg1.cfa(pred, None) + right_node = self.cond.arg2.cfa(left_node, None) + comp_node = CFG_Node(self.cond) + comp_node.label = f"({str(self.cond.arg1)} {self.cond.operator} {str(self.cond.arg2)})" + right_node.add_child(comp_node) + else: + # This is a simple condition (e.g., constant true/false or single expression) + cond_node = self.cond.cfa(pred, None) + comp_node = cond_node # Create the diamond node diamond = CFG_DIAMOND(self.cond) @@ -116,11 +135,14 @@ class WHILE(compiler.WHILE): body_entry = CFG_Node() diamond.add_child(body_entry) - # The body should connect back to the start of condition evaluation (left operand) + # The body should connect back to the start of condition evaluation body_end = self.body.cfa(body_entry, None) if body_end is not None: - # Connect body end back to the left operand (start of condition evaluation) - body_end.add_child(left_node) + # Connect body end back to the condition evaluation + if hasattr(self.cond, 'arg1') and hasattr(self.cond, 'arg2'): + body_end.add_child(left_node) + else: + body_end.add_child(pred) # For simple conditions, go back to start after = CFG_Node() diamond.add_child(after) @@ -129,14 +151,15 @@ class WHILE(compiler.WHILE): class CALL(compiler.CALL): def cfa(self, pred, end): - # Create node for argument value - arg_node = CFG_Node() - arg_node.label = str(self.arg[0]) # Assuming single argument for now - pred.add_child(arg_node) + # Create nodes for all argument values + current_arg_node = pred + for i, arg in enumerate(self.arg): + # Process argument through its cfa method to create proper CFG structure + current_arg_node = arg.cfa(current_arg_node, None) call_node = CFG_CALL(self) - call_node.label = f"CALL {self.f_name}({', '.join(map(str, self.arg))})" - arg_node.add_child(call_node) + call_node.label = f"CALL {self.f_name}" + current_arg_node.add_child(call_node) cont = CFG_Node() cont.add_child(end) if end else None @@ -148,7 +171,7 @@ class CALL(compiler.CALL): # Create return node from function return_node = CFG_RETURN(self) - return_node.label = f"RET {self.f_name}({', '.join(map(str, self.arg))})" + return_node.label = f"RET {self.f_name}" f_end.add_child(return_node) return_node.add_child(cont) @@ -156,23 +179,29 @@ class CALL(compiler.CALL): # Add direct edge from CALL to RET node (for the expected structure) call_node.add_child(return_node) - # For recursive calls in function g, the RET node should connect to the x variable - # This handles the specific case where g(y) return value flows to x + # For recursive calls, we need to ensure proper return value flow + # In expressions like g(x)+x, the return value from g(x) flows to the continuation + # This is especially important for recursive functions where multiple calls return values + # that need to flow to the same continuation point if self.f_name == 'g': - # We need to connect to the existing x variable node - # This will be handled in the CFG generation by connecting to the appropriate variable - pass + # For recursive calls in g, ensure the return node connects to continuation + # This handles cases like g(y) where the return value flows to the same place as g(x) + return_node.add_child(cont) return cont class DECL(compiler.DECL): def cfa(self, pred, end): - f_start = CFG_START(self) - f_start.label = f"START {self.f_name}({', '.join(self.params)})" - f_end = CFG_END(self) - f_end.label = f"END {self.f_name}({', '.join(self.params)})" - - FUNCTIONS[self.f_name] = (f_start, f_end) + # Check if function is already registered (from first pass in LET) + if self.f_name in FUNCTIONS: + f_start, f_end = FUNCTIONS[self.f_name] + else: + f_start = CFG_START(self) + f_start.label = f"START {self.f_name}({', '.join(self.params)})" + f_end = CFG_END(self) + f_end.label = f"END {self.f_name}({', '.join(self.params)})" + FUNCTIONS[self.f_name] = (f_start, f_end) + body_end = self.body.cfa(f_start, f_end) if body_end is not None: body_end.add_child(f_end) @@ -180,13 +209,25 @@ class DECL(compiler.DECL): class LET(compiler.LET): def cfa(self, pred, end): + # First pass: Register all function declarations + decls = self.decl if isinstance(self.decl, list) else [self.decl] + for d in decls: + if isinstance(d, compiler.DECL): + # Register function without building CFG yet + f_start = CFG_START(d) + f_start.label = f"START {d.f_name}({', '.join(d.params)})" + f_end = CFG_END(d) + f_end.label = f"END {d.f_name}({', '.join(d.params)})" + FUNCTIONS[d.f_name] = (f_start, f_end) + # Create global entry node global_entry = CFG_Node() global_entry.label = "None" pred.add_child(global_entry) current = global_entry - decls = self.decl if isinstance(self.decl, list) else [self.decl] + + # Second pass: Process declarations and build CFGs for d in decls: current = d.cfa(current, None) if current is None: @@ -200,7 +241,8 @@ class LET(compiler.LET): global_exit.label = "None" if body_result is not None: body_result.add_child(global_exit) - global_exit.add_child(end) + if end is not None: + global_exit.add_child(end) return global_exit diff --git a/Project-02-03-04/cfg_examples/example.dot b/Project-02-03-04/cfg_examples/example.dot index ad18bc0..09fd28c 100644 --- a/Project-02-03-04/cfg_examples/example.dot +++ b/Project-02-03-04/cfg_examples/example.dot @@ -1,4 +1,6 @@ digraph CFG { + n0 [label="3", shape="box"]; + n0 -> n36; node [fontname="Helvetica"]; n36 [label="2", shape=box]; n36 -> n37; diff --git a/Project-02-03-04/current_cfg.dot b/Project-02-03-04/current_cfg.dot deleted file mode 100644 index 22eeda1..0000000 --- a/Project-02-03-04/current_cfg.dot +++ /dev/null @@ -1,57 +0,0 @@ -digraph CFG { - node [fontname="Helvetica"]; - n32 [label="3", shape=box]; - n32 -> n33; - n33 [label="CALL f(3)", shape=box, style=filled, color=orange]; - n33 -> n4; - n4 [label="START f(x)", shape=box, style=filled, color=green]; - n4 -> n6; - n6 [label="2", shape=box]; - n6 -> n7; - n7 [label="x", shape=box]; - n7 -> n8; - n8 [label="(2 * x)", shape=box]; - n8 -> n9; - n9 [label="x = (2 * x)", shape=box]; - n9 -> n10; - n10 [label="x", shape=box]; - n10 -> n11; - n11 [label="0", shape=box]; - n11 -> n12; - n12 [label="(x > 0)", shape=box]; - n12 -> n13; - n13 [label="<>", shape=diamond]; - n13 -> n17 [label="T"]; - n17 [label="x", shape=box]; - n17 -> n18; - n18 [label="1", shape=box]; - n18 -> n19; - n19 [label="(x - 1)", shape=box]; - n19 -> n20; - n20 [label="x = (x - 1)", shape=box]; - n20 -> n22; - n22 [label="x", shape=box]; - n22 -> n23; - n23 [label="0", shape=box]; - n23 -> n24; - n24 [label="(x > 0)", shape=box]; - n24 -> n25; - n25 [label="<>", shape=diamond]; - n25 -> n27 [label="T"]; - n27 [label="x", shape=box]; - n27 -> n28; - n28 [label="1", shape=box]; - n28 -> n29; - n29 [label="(x - 1)", shape=box]; - n29 -> n30; - n30 [label="x = (x - 1)", shape=box]; - n30 -> n25; - n25 -> n5 [label="F"]; - n5 [label="END f(x)", shape=box, style=filled, color=green]; - n5 -> n35; - n35 [label="RET f(3)", shape=box, style=filled, color=orange]; - n13 -> n21 [label="F"]; - n21 [label="x", shape=box]; - n21 -> n22; - n33 -> n35; -} \ No newline at end of file diff --git a/Project-02-03-04/example_cfg.dot b/Project-02-03-04/example_cfg.dot deleted file mode 100644 index e0c5c04..0000000 --- a/Project-02-03-04/example_cfg.dot +++ /dev/null @@ -1,63 +0,0 @@ -digraph CFG { - node [fontname="Helvetica"]; - n36 [label="2", shape=box]; - n36 -> n37; - n37 [label="CALL f(2, 3)", shape=box, style=filled, color=orange]; - n37 -> n4; - n4 [label="START f(x, y, z)", shape=box, style=filled, color=green]; - n4 -> n6; - n6 [label="2", shape=box]; - n6 -> n7; - n7 [label="y = 2", shape=box]; - n7 -> n8; - n8 [label="3", shape=box]; - n8 -> n9; - n9 [label="z = 3", shape=box]; - n9 -> n29; - n29 [label="x", shape=box]; - n29 -> n30; - n30 [label="CALL g(x)", shape=box, style=filled, color=orange]; - n30 -> n11; - n11 [label="START g(x)", shape=box, style=filled, color=green]; - n11 -> n13; - n13 [label="7", shape=box]; - n13 -> n14; - n14 [label="x = 7", shape=box]; - n14 -> n15; - n15 [label="y", shape=box]; - n15 -> n16; - n16 [label="0", shape=box]; - n16 -> n17; - n17 [label="(y > 0)", shape=box]; - n17 -> n18; - n18 [label="<>", shape=diamond]; - n18 -> n22 [label="T"]; - n22 [label="y", shape=box]; - n22 -> n23; - n23 [label="CALL g(y)", shape=box, style=filled, color=orange]; - n23 -> n11; - n23 -> n25; - n25 [label="RET g(y)", shape=box, style=filled, color=orange]; - n28 [label="x", shape=box]; - n28 -> n12; - n12 [label="END g(x)", shape=box, style=filled, color=green]; - n12 -> n25; - n12 -> n32; - n32 [label="RET g(x)", shape=box, style=filled, color=orange]; - n32 -> n33; - n33 [label="x", shape=box]; - n33 -> n34; - n34 [label="(g(x) + x)", shape=box]; - n34 -> n5; - n5 [label="END f(x, y, z)", shape=box, style=filled, color=green]; - n5 -> n39; - n39 [label="RET f(2, 3)", shape=box, style=filled, color=orange]; - n34 -> n5; - n18 -> n26 [label="F"]; - n26 [label="8", shape=box]; - n26 -> n27; - n27 [label="x = 8", shape=box]; - n27 -> n28; - n30 -> n32; - n37 -> n39; -} \ No newline at end of file diff --git a/Project-02-03-04/final_cfg.dot b/Project-02-03-04/final_cfg.dot deleted file mode 100644 index 7c78faa..0000000 --- a/Project-02-03-04/final_cfg.dot +++ /dev/null @@ -1,57 +0,0 @@ -digraph CFG { - node [fontname="Helvetica"]; - n32 [label="3", shape=box]; - n32 -> n33; - n33 [label="CALL f(3)", shape=box, style=filled, color=orange]; - n33 -> n4; - n4 [label="START f(x)", shape=box, style=filled, color=green]; - n4 -> n6; - n6 [label="2", shape=box]; - n6 -> n7; - n7 [label="x", shape=box]; - n7 -> n8; - n8 [label="(2 * x)", shape=box]; - n8 -> n9; - n9 [label="x = (2 * x)", shape=box]; - n9 -> n10; - n10 [label="x", shape=box]; - n10 -> n11; - n11 [label="0", shape=box]; - n11 -> n12; - n12 [label="(x > 0)", shape=box]; - n12 -> n13; - n13 [label="<>", shape=diamond]; - n13 -> n17 [label="T"]; - n17 [label="x", shape=box]; - n17 -> n18; - n18 [label="1", shape=box]; - n18 -> n19; - n19 [label="(x - 1)", shape=box]; - n19 -> n20; - n20 [label="x = (x - 1)", shape=box]; - n20 -> n22; - n22 [label="x", shape=box]; - n22 -> n23; - n23 [label="0", shape=box]; - n23 -> n24; - n24 [label="(x > 0)", shape=box]; - n24 -> n25; - n25 [label="<>", shape=diamond]; - n25 -> n27 [label="T"]; - n27 [label="x", shape=box]; - n27 -> n28; - n28 [label="1", shape=box]; - n28 -> n29; - n29 [label="(x - 1)", shape=box]; - n29 -> n30; - n30 [label="x = (x - 1)", shape=box]; - n30 -> n22; - n25 -> n5 [label="F"]; - n5 [label="END f(x)", shape=box, style=filled, color=green]; - n5 -> n35; - n35 [label="RET f(3)", shape=box, style=filled, color=orange]; - n13 -> n21 [label="F"]; - n21 [label="x", shape=box]; - n21 -> n22; - n33 -> n35; -} \ No newline at end of file