finish refactoring task 1 and 2
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 374 KiB After Width: | Height: | Size: 356 KiB |
@@ -25,7 +25,7 @@ class BackwardAnalysis:
|
||||
|
||||
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.func_scope: dict[int, str] = self.__compute_function_scope()
|
||||
self.__extract_uses_and_defs()
|
||||
|
||||
# Walk the AST and collect function-parent and parameter information.
|
||||
@@ -90,7 +90,7 @@ class BackwardAnalysis:
|
||||
def __extract_uses_and_defs(self) -> None:
|
||||
for node in self.cfg.nodes():
|
||||
nid = node.id
|
||||
func = self.__func_scope.get(nid)
|
||||
func = self.func_scope.get(nid)
|
||||
ast = node.ast_node
|
||||
|
||||
uses: set[Var] = set()
|
||||
@@ -102,17 +102,17 @@ class BackwardAnalysis:
|
||||
defs.add((ast.f_name, param))
|
||||
elif ast is not None:
|
||||
if isinstance(ast, syntax.ID):
|
||||
resolved = self.__resolve_var(func, ast.name)
|
||||
resolved = self.resolve_var(func, ast.name)
|
||||
uses.add(resolved)
|
||||
elif isinstance(ast, syntax.ASSIGN):
|
||||
resolved = self.__resolve_var(func, ast.var.name)
|
||||
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:
|
||||
def resolve_var(self, func: str | None, name: str) -> Var:
|
||||
if func is None:
|
||||
return GLOBAL_SCOPE, name
|
||||
|
||||
|
||||
@@ -2,8 +2,38 @@ import syntax
|
||||
import colorsys
|
||||
from cfg.CFG_Node import CFG_DIAMOND
|
||||
|
||||
# Builds annotations for the LiveVariables analysis.
|
||||
def build_lv_annotations(cfg, lv) -> dict[int, str]:
|
||||
node_by_id = {n.id: n for n in cfg.nodes()}
|
||||
all_ids = set(lv.incoming.keys()) | set(lv.outgoing.keys())
|
||||
return {
|
||||
nid: (
|
||||
"LivingVariables\\n"
|
||||
f"In := {sorted(__lv_in_set(node_by_id[nid], lv))}\\n"
|
||||
f"Out := {sorted(lv.outgoing.get(nid, set()))}"
|
||||
)
|
||||
for nid in all_ids
|
||||
if lv.incoming.get(nid, set()) or lv.outgoing.get(nid, set())
|
||||
if nid in node_by_id and __should_display_analysis(node_by_id[nid])
|
||||
}
|
||||
|
||||
# For display only: IN(ASSIGN) has GEN = empty, so RHS variables are missing from incoming — they live at their
|
||||
# own ID nodes. Add them here for a proper DOT annotation.
|
||||
def __lv_in_set(node, analysis):
|
||||
in_set = set(analysis.incoming.get(node.id, set()))
|
||||
ast_node = node.ast_node
|
||||
if isinstance(ast_node, syntax.ASSIGN):
|
||||
func = analysis.func_scope.get(node.id)
|
||||
rhs_vars = {
|
||||
analysis.resolve_var(func, name)
|
||||
for name in __expr_used_names(ast_node.expr)
|
||||
}
|
||||
in_set |= rhs_vars
|
||||
return in_set
|
||||
|
||||
# For display only: the right-hand side of an ASSIGN has no dedicated CFG nodes, so LV places uses of "a", "b"
|
||||
# at their own nodes — not at the ASSIGN. Recover them here to complete the DOT annotation at the ASSIGN node.
|
||||
def __expr_used_names(expr) -> set[str]:
|
||||
"""Collect variable names (syntax.ID) used inside an expression subtree."""
|
||||
used: set[str] = set()
|
||||
|
||||
def visit(node):
|
||||
@@ -42,23 +72,8 @@ def __should_display_analysis(node) -> bool:
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def _lv_in_for_display(node, analysis):
|
||||
"""Display-level IN set for LV."""
|
||||
in_set = set(analysis.incoming.get(node.id, set()))
|
||||
ast_node = node.ast_node
|
||||
if isinstance(ast_node, syntax.ASSIGN):
|
||||
func = analysis.__func_scope.get(node.id)
|
||||
rhs_vars = {
|
||||
analysis.__resolve_var(func, name)
|
||||
for name in __expr_used_names(ast_node.expr)
|
||||
}
|
||||
in_set |= rhs_vars
|
||||
return in_set
|
||||
|
||||
|
||||
def _node_color(node_id: int) -> tuple[str, str]:
|
||||
"""Return (edge_color, fill_color) deterministically for a node id."""
|
||||
# Generates colors for CFG nodes based on their id.
|
||||
def __node_color(node_id: int) -> tuple[str, str]:
|
||||
# Golden-angle hue distribution gives stable, distinct colors.
|
||||
hue = ((node_id * 0.6180339887498949) % 1.0)
|
||||
edge_rgb = colorsys.hsv_to_rgb(hue, 0.70, 0.82)
|
||||
@@ -70,54 +85,27 @@ def _node_color(node_id: int) -> tuple[str, str]:
|
||||
|
||||
return to_hex(edge_rgb), to_hex(fill_rgb)
|
||||
|
||||
# Return a DOT string for the CFG annotated with analysis results.
|
||||
def analysis_to_dot(cfg, analyses: dict, analysis_name: str) -> str:
|
||||
ru = analyses.get("ru")
|
||||
lv = analyses.get("lv")
|
||||
|
||||
def run_all_analyses(cfg):
|
||||
"""Run Live Variables and Reached Uses on *cfg*.
|
||||
ru_edges = ru.reached_uses_by_node() if ru is not None else None
|
||||
|
||||
Returns ``(analyses, annotations, ru_edges)`` where:
|
||||
• *analyses* is a dict with keys ``"lv"`` and ``"ru"``,
|
||||
• *annotations* contains only LivingVariables helper-node labels,
|
||||
• *ru_edges* maps definition-node ids to reached use-node ids.
|
||||
"""
|
||||
node_by_id = {n.id: n for n in cfg.nodes()}
|
||||
|
||||
from cfa.LiveVariables import LiveVariablesAnalysis
|
||||
from cfa.ReachedUses import ReachedUsesAnalysis
|
||||
|
||||
lv = LiveVariablesAnalysis(cfg)
|
||||
ru = ReachedUsesAnalysis(cfg)
|
||||
|
||||
all_ids = set(lv.incoming.keys()) | set(lv.outgoing.keys())
|
||||
annotations = {
|
||||
nid: (
|
||||
"LivingVariables\\n"
|
||||
f"In := {sorted(_lv_in_for_display(node_by_id[nid], lv))}\\n"
|
||||
f"Out := {sorted(lv.outgoing.get(nid, set()))}"
|
||||
)
|
||||
for nid in all_ids
|
||||
if lv.incoming.get(nid, set()) or lv.outgoing.get(nid, set())
|
||||
if nid in node_by_id and __should_display_analysis(node_by_id[nid])
|
||||
}
|
||||
|
||||
return {"lv": lv, "ru": ru}, annotations, ru.reached_uses_by_node()
|
||||
|
||||
|
||||
def analysis_to_dot(
|
||||
cfg,
|
||||
annotations: dict[int, str],
|
||||
analysis_name: str,
|
||||
ru_edges: dict[int, list[int]] | None = None,
|
||||
) -> str:
|
||||
"""Return a DOT string for *cfg* annotated with analysis results."""
|
||||
# DOT graph header
|
||||
lines = [
|
||||
"digraph CFG {",
|
||||
f' // Analysis: {analysis_name}',
|
||||
' graph [splines=ortho, overlap=false, ranksep=0.7, nodesep=0.45];',
|
||||
' node [fontname="Helvetica"];',
|
||||
]
|
||||
color_nodes = set(annotations.keys()) | set((ru_edges or {}).keys())
|
||||
node_colors = {nid: _node_color(nid) for nid in color_nodes}
|
||||
|
||||
# Build LV annotations and assign a color per annotated node
|
||||
annotations = build_lv_annotations(cfg, lv)
|
||||
color_nodes = set(annotations.keys()) | set((ru_edges or {}).keys())
|
||||
node_colors = {nid: __node_color(nid) for nid in color_nodes}
|
||||
|
||||
# Emit each CFG node with its label, shape, and optional LV annotation note
|
||||
def emit(node):
|
||||
base_label = node.dot_label() or ""
|
||||
shape = node.dot_shape
|
||||
@@ -150,6 +138,7 @@ def analysis_to_dot(
|
||||
|
||||
cfg.traverse(emit, start=cfg.START)
|
||||
|
||||
# Draw dashed def -> use edges for Reached Uses
|
||||
if ru_edges:
|
||||
for idx, def_id in enumerate(sorted(ru_edges)):
|
||||
use_ids = sorted(set(ru_edges[def_id]))
|
||||
|
||||
@@ -11,7 +11,9 @@ import cfg_build
|
||||
import lib.console as cnsl
|
||||
import syntax
|
||||
import triplayacc as yacc
|
||||
from cfa.to_dot import analysis_to_dot, run_all_analyses
|
||||
from cfa.LiveVariables import LiveVariables
|
||||
from cfa.ReachedUses import ReachedUses
|
||||
from cfa.to_dot import analysis_to_dot
|
||||
from cfg.CFG import CFG
|
||||
from vistram.tram import *
|
||||
from vistram.vistram import MachineUI
|
||||
@@ -64,12 +66,11 @@ def pretty_print(node, indent=0):
|
||||
else:
|
||||
print(f"{prefix} {key}: {value}")
|
||||
|
||||
|
||||
def print_analysis_reports(cfg, analyses: dict, ru_edges: dict[int, list[int]]):
|
||||
"""Print compact Live Variables and Reached Uses reports to console."""
|
||||
# Print compact Live Variables and Reached Uses reports to console.
|
||||
def print_analysis_reports(cfg, analyses: dict):
|
||||
lv = analyses["lv"]
|
||||
ru = analyses["ru"]
|
||||
_ = ru
|
||||
ru_edges = ru.reached_uses_by_node()
|
||||
node_by_id = {n.id: n for n in cfg.nodes()}
|
||||
|
||||
def node_text(nid: int) -> str:
|
||||
@@ -102,7 +103,6 @@ def print_analysis_reports(cfg, analyses: dict, ru_edges: dict[int, list[int]]):
|
||||
if not has_ru:
|
||||
print("(no reached uses)")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("\nTRIPLA Parser and TRIPLA to TRAM Compiler")
|
||||
|
||||
@@ -211,28 +211,25 @@ if __name__ == "__main__":
|
||||
print("Rendered CFG diagram.")
|
||||
|
||||
elif mode == 3:
|
||||
# Reset global CFG builder state so each analysis run is clean
|
||||
# Reset the global CFG builder state so each analysis run is clean
|
||||
cfg_build.FUNCTIONS.clear()
|
||||
cfg_build.CURRENT_FUNCTION = None
|
||||
|
||||
cfg = make_cfg(ast)
|
||||
analysis_name = "Live Variables + Reached Uses"
|
||||
analysis_name = "Live Variables + Reached Uses analyses"
|
||||
print(f"\nRunning {analysis_name} …")
|
||||
try:
|
||||
_analyses, annotations, ru_edges = run_all_analyses(cfg)
|
||||
lv = LiveVariables(cfg)
|
||||
ru = ReachedUses(cfg)
|
||||
analyses = {"lv": lv, "ru": ru}
|
||||
except Exception as exc:
|
||||
print(f"Analysis failed: {exc}")
|
||||
continue
|
||||
|
||||
print(
|
||||
f"Done. {len(annotations)} LV annotation node(s), "
|
||||
f"{len(ru_edges)} RU definition node(s)."
|
||||
)
|
||||
|
||||
dot_str = analysis_to_dot(cfg, annotations, analysis_name, ru_edges)
|
||||
|
||||
if cnsl.prompt_confirmation("\nPrint analysis reports to console?"):
|
||||
print_analysis_reports(cfg, _analyses, ru_edges)
|
||||
print_analysis_reports(cfg, analyses)
|
||||
|
||||
dot_str = analysis_to_dot(cfg, analyses, analysis_name)
|
||||
|
||||
if cnsl.prompt_confirmation("\nExport annotated CFG as .dot file?"):
|
||||
default = f"{path.stem}_analysis.dot"
|
||||
|
||||
Reference in New Issue
Block a user