diff --git a/Project-02-03-04/Source.gv.png b/Project-02-03-04/Source.gv.png index 54832a6..57d6048 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_build.py b/Project-02-03-04/cfg_build.py index 592acfe..482959f 100644 --- a/Project-02-03-04/cfg_build.py +++ b/Project-02-03-04/cfg_build.py @@ -14,159 +14,202 @@ import syntax FUNCTIONS = {} class CONST(compiler.CONST): - def cfa(self, pred, end): - n = CFG_Node(self) - pred.add_child(n) - n.add_child(end) if end else None - return n + def cfa(self, pred, end = None): + node = CFG_Node(self) + pred.add_child(node) + + # Attach the end node if it is provided + if end is not None: + node.add_child(end) + return node class ID(compiler.ID): - def cfa(self, pred, end): - n = CFG_Node(self) - pred.add_child(n) - n.add_child(end) if end else None - return n + def cfa(self, pred, end = None): + node = CFG_Node(self) + pred.add_child(node) + + # Attach the end node if it is provided + if end is not None: + node.add_child(end) + return node 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) + def cfa(self, pred, end = None): + # Create nodes for the used expressions and attach + left_node = self.arg1.cfa(pred) + right_node = self.arg2.cfa(left_node) - # Create the comparison node with just the operator + # Create the operator node and attach op_node = CFG_Node(self) - op_node.label = f"{self.operator}" + op_node.label = f"{str(self.arg1)} {self.operator} {str(self.arg2)}" right_node.add_child(op_node) - op_node.add_child(end) if end else None + + # Attach the end node if it is provided + if end is not None: + op_node.add_child(end) return op_node class COMP(compiler.COMP): - 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) + def cfa(self, pred, end = None): + # Create nodes for the used expressions and attach + left_node = self.arg1.cfa(pred) + right_node = self.arg2.cfa(left_node) - # Create the comparison node with just the operator + # Create the comparison node and attach comp_node = CFG_Node(self) - comp_node.label = f"{self.operator}" + comp_node.label = f"{str(self.arg1)} {self.operator} {str(self.arg2)}" right_node.add_child(comp_node) - comp_node.add_child(end) if end else None + + # Attach the end node if it is provided + if end is not None: + comp_node.add_child(end) 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) + def cfa(self, pred, end = None): + # Create nodes for the used expressions and attach + left_node = self.arg1.cfa(pred) + right_node = self.arg2.cfa(left_node) - # Create the equation node with just the operator + # Create the comparison node and attach eqop_node = CFG_Node(self) - eqop_node.label = f"{self.operator}" + eqop_node.label = f"{str(self.arg1)} {self.operator} {str(self.arg2)}" right_node.add_child(eqop_node) - eqop_node.add_child(end) if end else None + + # Attach the end node if it is provided + if end is not None: + eqop_node.add_child(end) return eqop_node class LOP(compiler.LOP): - def cfa(self, pred, end): + def cfa(self, pred, end = None): # Create nodes for each operand separately - left_node = self.arg1.cfa(pred, None) - right_node = self.arg2.cfa(left_node, None) + left_node = self.arg1.cfa(pred) + right_node = self.arg2.cfa(left_node) # Create the logical operation node with just the operator lop_node = CFG_Node(self) - lop_node.label = f"{self.operator}" + lop_node.label = f"{str(self.arg1)} {self.operator} {str(self.arg2)}" right_node.add_child(lop_node) - lop_node.add_child(end) if end else None + + # Attach the end node if it is provided + if end is not None: + lop_node.add_child(end) return lop_node class ASSIGN(compiler.ASSIGN): - def cfa(self, pred, end): - expr_node = self.expr.cfa(pred, None) + def cfa(self, pred, end = None): + # Unwraps expressions needed for assignment + expr_node = self.expr.cfa(pred) + + # Assignment node assign_node = CFG_Node(self) expr_node.add_child(assign_node) - assign_node.add_child(end) if end else None + + # Attach the end node if it is provided + if end is not None: + assign_node.add_child(end) return assign_node class SEQ(compiler.SEQ): - def cfa(self, pred, end): - mid = self.exp1.cfa(pred, None) + def cfa(self, pred, end = None): + mid = self.exp1.cfa(pred) if mid is None: return None return self.exp2.cfa(mid, end) class IF(compiler.IF): - def cfa(self, pred, end): + def cfa(self, pred, end = None): + # Unwraps expressions needed for the condition cond_node = self.cond.cfa(pred, None) + + # Attach junction node diamond = CFG_DIAMOND(self.cond) - diamond.label = "" # Use simple diamond label + diamond.label = "" cond_node.add_child(diamond) + + # Define start and end entry and unwraps expressions then_entry = CFG_Node() - else_entry = CFG_Node() diamond.add_child(then_entry) + else_entry = CFG_Node() diamond.add_child(else_entry) + + # Attach the end node if it is provided join = CFG_Node() - join.add_child(end) if end else None + if end is not None: + join.add_child(end) + + # Connect the extracted expressions with the join then_end = self.exp1.cfa(then_entry, join) else_end = self.exp2.cfa(else_entry, join) return join class WHILE(compiler.WHILE): - def cfa(self, pred, end): - # Handle different types of conditions + def cfa(self, pred, end = None): 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) + left_node = self.cond.arg1.cfa(pred) + right_node = self.cond.arg2.cfa(left_node) + + # Create the comparison node and attach comp_node = CFG_Node(self.cond) - comp_node.label = f"({str(self.cond.arg1)} {self.cond.operator} {str(self.cond.arg2)})" + 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) + # This is a simple condition (e.g., constant true/false) + cond_node = self.cond.cfa(pred) comp_node = cond_node - # Create the diamond node + # Attach junction node diamond = CFG_DIAMOND(self.cond) - diamond.label = "<>" # Use simple diamond label + diamond.label = "" comp_node.add_child(diamond) - # For the true branch, go to body + # Unwrap the loop body body_entry = CFG_Node() diamond.add_child(body_entry) # The body should connect back to the start of condition evaluation - body_end = self.body.cfa(body_entry, None) + body_end = self.body.cfa(body_entry) if body_end is not None: - # Connect body end back to the condition evaluation + # Connect the 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 - + body_end.add_child(pred) + + # Attach joining node after = CFG_Node() diamond.add_child(after) - after.add_child(end) if end else None + + # Attach the end node if it is provided + if end is not None: + after.add_child(end) return after class CALL(compiler.CALL): - def cfa(self, pred, end): + def cfa(self, pred, end = None): # 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) - + current_arg_node = arg.cfa(current_arg_node) + + # Create and attach the call node call_node = CFG_CALL(self) call_node.label = f"CALL {self.f_name}" current_arg_node.add_child(call_node) + # Create and attach the exit node cont = CFG_Node() - cont.add_child(end) if end else None + if end is not None: + cont.add_child(end) + # Find the functions in the function list if self.f_name not in FUNCTIONS: raise RuntimeError(f"Call to undefined function '{self.f_name}'") - + # Determine start and exit node of the function f_start, f_end = FUNCTIONS[self.f_name] # Create return node from function @@ -175,10 +218,11 @@ class CALL(compiler.CALL): f_end.add_child(return_node) return_node.add_child(cont) + # Span the start and exit nodes to the method body call_node.add_child(f_start) - # Add direct edge from CALL to RET node (for the expected structure) call_node.add_child(return_node) - + + # TODO: Why only g? Also f can be recursive. # 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 @@ -192,23 +236,27 @@ class CALL(compiler.CALL): class DECL(compiler.DECL): def cfa(self, pred, end): - # Check if function is already registered (from first pass in LET) + # Check if a function is already registered if self.f_name in FUNCTIONS: f_start, f_end = FUNCTIONS[self.f_name] else: + # Span the method body into a start and end node 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) - + + # Unwrap the method body body_end = self.body.cfa(f_start, f_end) + + # Attach the end node if it is provided if body_end is not None: body_end.add_child(f_end) return pred class LET(compiler.LET): - def cfa(self, pred, end): + def cfa(self, pred, end = None): # First pass: Register all function declarations decls = self.decl if isinstance(self.decl, list) else [self.decl] for d in decls: @@ -220,20 +268,19 @@ class LET(compiler.LET): f_end.label = f"END {d.f_name}({', '.join(d.params)})" FUNCTIONS[d.f_name] = (f_start, f_end) - # Create global entry node + # Create a global entry node for the function global_entry = CFG_Node() global_entry.label = "None" pred.add_child(global_entry) - current = global_entry - # Second pass: Process declarations and build CFGs + # Generate function declarations for d in decls: current = d.cfa(current, None) if current is None: return None - # Process the body (function call) + # Unwrap the body body_result = self.body.cfa(current, end) # Create global exit node @@ -241,9 +288,10 @@ class LET(compiler.LET): global_exit.label = "None" if body_result is not None: body_result.add_child(global_exit) + + # Attach the end node if it is provided if end is not None: global_exit.add_child(end) - return global_exit class RETURN(syntax.EXPRESSION): diff --git a/Project-02-03-04/main.py b/Project-02-03-04/main.py index ad2bbc4..bee2650 100644 --- a/Project-02-03-04/main.py +++ b/Project-02-03-04/main.py @@ -35,7 +35,6 @@ def make_cfg(ast): return CFG(start, end) - # Renders a diagram of the AST def render_diagram(dot_string: str): # Set DPI for PNG