Start refactoring task 1 and 2
This commit is contained in:
128
Project-02-03-04-05/cfa/BackwardAnalysis.py
Normal file
128
Project-02-03-04-05/cfa/BackwardAnalysis.py
Normal file
@@ -0,0 +1,128 @@
|
||||
from __future__ import annotations
|
||||
from collections import deque
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import cfg_build
|
||||
import syntax
|
||||
from cfg.CFG_Node import CFG_START
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from cfg.CFG import CFG
|
||||
|
||||
GLOBAL_SCOPE = ""
|
||||
|
||||
# A scoped variable: the function it belongs to, and its name.
|
||||
# The scope is GLOBAL_SCOPE ("") for variables outside any function.
|
||||
# e.g. ("f", "x") → variable "x" defined in function "f"
|
||||
# ("", "x") → variable "x" at global scope
|
||||
Var = tuple[str, str]
|
||||
|
||||
class BackwardAnalysis:
|
||||
def __init__(self, cfg: "CFG") -> None:
|
||||
self.cfg = cfg
|
||||
self.uses: dict[int, set[Var]] = {}
|
||||
self.defs: dict[int, set[Var]] = {}
|
||||
|
||||
self.__funcs: dict[str, tuple] = dict(cfg_build.FUNCTIONS)
|
||||
self.__func_parent, self._func_params = self.__collect_function_metadata()
|
||||
self.__func_scope: dict[int, str] = self.__compute_function_scope()
|
||||
self.__extract_uses_and_defs()
|
||||
|
||||
# Walk the AST and collect function-parent and parameter information.
|
||||
def __collect_function_metadata(self) -> tuple[dict[str, str | None], dict[str, tuple[str, ...]]]:
|
||||
func_parent: dict[str, str | None] = {}
|
||||
func_params: dict[str, tuple[str, ...]] = {}
|
||||
|
||||
def visit(expr: syntax.EXPRESSION | None, current_func: str | None) -> None:
|
||||
if expr is None:
|
||||
return
|
||||
if isinstance(expr, syntax.LET):
|
||||
decls = expr.decl if isinstance(expr.decl, list) else [expr.decl]
|
||||
# Register metadata for each declared function.
|
||||
for d in decls:
|
||||
if isinstance(d, syntax.DECL):
|
||||
func_parent[d.f_name] = current_func
|
||||
func_params[d.f_name] = tuple(d.params)
|
||||
# Recurse into function bodies and the in-expression.
|
||||
for d in decls:
|
||||
if isinstance(d, syntax.DECL):
|
||||
visit(d.body, d.f_name)
|
||||
else:
|
||||
visit(d, current_func)
|
||||
visit(expr.body, current_func)
|
||||
return
|
||||
for _, child in expr.children():
|
||||
visit(child, current_func)
|
||||
|
||||
visit(self.cfg.ast, None)
|
||||
return func_parent, func_params
|
||||
|
||||
# Calculates the scope (in which function is it?) of each node in the CFG.
|
||||
def __compute_function_scope(self) -> dict[int, str]:
|
||||
# The first function whose BFS claims a node wins.
|
||||
functions = self.__funcs
|
||||
func_scope: dict[int, str] = {}
|
||||
all_f_start_ids: set[int] = {fs.id for _, (fs, _) in functions.items()}
|
||||
|
||||
for f_name, (f_start, f_end) in functions.items():
|
||||
queue: deque = deque([f_start])
|
||||
while queue:
|
||||
node = queue.popleft()
|
||||
if node.id in func_scope:
|
||||
continue # already claimed by an earlier function
|
||||
func_scope[node.id] = f_name
|
||||
# Stop here — do not follow into the caller context.
|
||||
if node.id == f_end.id:
|
||||
continue
|
||||
for child in node.children:
|
||||
# Do not follow into a different function's START.
|
||||
if (
|
||||
isinstance(child, CFG_START)
|
||||
and child.id in all_f_start_ids
|
||||
and child.id != f_start.id
|
||||
):
|
||||
continue
|
||||
queue.append(child)
|
||||
|
||||
return func_scope
|
||||
|
||||
# Populate uses and defs for every node in the CFG.
|
||||
def __extract_uses_and_defs(self) -> None:
|
||||
for node in self.cfg.nodes():
|
||||
nid = node.id
|
||||
func = self.__func_scope.get(nid)
|
||||
ast = node.ast_node
|
||||
|
||||
uses: set[Var] = set()
|
||||
defs: set[Var] = set()
|
||||
|
||||
if isinstance(node, CFG_START) and isinstance(ast, syntax.DECL):
|
||||
# Function entry defines each formal parameter.
|
||||
for param in ast.params:
|
||||
defs.add((ast.f_name, param))
|
||||
elif ast is not None:
|
||||
if isinstance(ast, syntax.ID):
|
||||
resolved = self.__resolve_var(func, ast.name)
|
||||
uses.add(resolved)
|
||||
elif isinstance(ast, syntax.ASSIGN):
|
||||
resolved = self.__resolve_var(func, ast.var.name)
|
||||
defs.add(resolved)
|
||||
|
||||
self.uses[nid] = uses
|
||||
self.defs[nid] = defs
|
||||
|
||||
# Resolve a variables name and scope by walking up the hierarchy
|
||||
def __resolve_var(self, func: str | None, name: str) -> Var:
|
||||
if func is None:
|
||||
return GLOBAL_SCOPE, name
|
||||
|
||||
cur: str | None = func
|
||||
seen: set[str] = set()
|
||||
while cur is not None and cur not in seen:
|
||||
seen.add(cur)
|
||||
if name in self._func_params.get(cur, ()):
|
||||
return cur, name
|
||||
cur = self.__func_parent.get(cur)
|
||||
|
||||
# Fallback: local variable in the current function scope
|
||||
return func, name
|
||||
Reference in New Issue
Block a user