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.__funcs: dict[str, tuple] = dict(cfg_build.FUNCTIONS)
|
||||||
self.__func_parent, self._func_params = self.__collect_function_metadata()
|
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()
|
self.__extract_uses_and_defs()
|
||||||
|
|
||||||
# Walk the AST and collect function-parent and parameter information.
|
# Walk the AST and collect function-parent and parameter information.
|
||||||
@@ -90,7 +90,7 @@ class BackwardAnalysis:
|
|||||||
def __extract_uses_and_defs(self) -> None:
|
def __extract_uses_and_defs(self) -> None:
|
||||||
for node in self.cfg.nodes():
|
for node in self.cfg.nodes():
|
||||||
nid = node.id
|
nid = node.id
|
||||||
func = self.__func_scope.get(nid)
|
func = self.func_scope.get(nid)
|
||||||
ast = node.ast_node
|
ast = node.ast_node
|
||||||
|
|
||||||
uses: set[Var] = set()
|
uses: set[Var] = set()
|
||||||
@@ -102,17 +102,17 @@ class BackwardAnalysis:
|
|||||||
defs.add((ast.f_name, param))
|
defs.add((ast.f_name, param))
|
||||||
elif ast is not None:
|
elif ast is not None:
|
||||||
if isinstance(ast, syntax.ID):
|
if isinstance(ast, syntax.ID):
|
||||||
resolved = self.__resolve_var(func, ast.name)
|
resolved = self.resolve_var(func, ast.name)
|
||||||
uses.add(resolved)
|
uses.add(resolved)
|
||||||
elif isinstance(ast, syntax.ASSIGN):
|
elif isinstance(ast, syntax.ASSIGN):
|
||||||
resolved = self.__resolve_var(func, ast.var.name)
|
resolved = self.resolve_var(func, ast.var.name)
|
||||||
defs.add(resolved)
|
defs.add(resolved)
|
||||||
|
|
||||||
self.uses[nid] = uses
|
self.uses[nid] = uses
|
||||||
self.defs[nid] = defs
|
self.defs[nid] = defs
|
||||||
|
|
||||||
# Resolve a variables name and scope by walking up the hierarchy
|
# 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:
|
if func is None:
|
||||||
return GLOBAL_SCOPE, name
|
return GLOBAL_SCOPE, name
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,38 @@ import syntax
|
|||||||
import colorsys
|
import colorsys
|
||||||
from cfg.CFG_Node import CFG_DIAMOND
|
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]:
|
def __expr_used_names(expr) -> set[str]:
|
||||||
"""Collect variable names (syntax.ID) used inside an expression subtree."""
|
|
||||||
used: set[str] = set()
|
used: set[str] = set()
|
||||||
|
|
||||||
def visit(node):
|
def visit(node):
|
||||||
@@ -42,23 +72,8 @@ def __should_display_analysis(node) -> bool:
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Generates colors for CFG nodes based on their id.
|
||||||
def _lv_in_for_display(node, analysis):
|
def __node_color(node_id: int) -> tuple[str, str]:
|
||||||
"""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."""
|
|
||||||
# Golden-angle hue distribution gives stable, distinct colors.
|
# Golden-angle hue distribution gives stable, distinct colors.
|
||||||
hue = ((node_id * 0.6180339887498949) % 1.0)
|
hue = ((node_id * 0.6180339887498949) % 1.0)
|
||||||
edge_rgb = colorsys.hsv_to_rgb(hue, 0.70, 0.82)
|
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 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):
|
ru_edges = ru.reached_uses_by_node() if ru is not None else None
|
||||||
"""Run Live Variables and Reached Uses on *cfg*.
|
|
||||||
|
|
||||||
Returns ``(analyses, annotations, ru_edges)`` where:
|
# DOT graph header
|
||||||
• *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."""
|
|
||||||
lines = [
|
lines = [
|
||||||
"digraph CFG {",
|
"digraph CFG {",
|
||||||
f' // Analysis: {analysis_name}',
|
f' // Analysis: {analysis_name}',
|
||||||
' graph [splines=ortho, overlap=false, ranksep=0.7, nodesep=0.45];',
|
' graph [splines=ortho, overlap=false, ranksep=0.7, nodesep=0.45];',
|
||||||
' node [fontname="Helvetica"];',
|
' 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):
|
def emit(node):
|
||||||
base_label = node.dot_label() or ""
|
base_label = node.dot_label() or ""
|
||||||
shape = node.dot_shape
|
shape = node.dot_shape
|
||||||
@@ -150,6 +138,7 @@ def analysis_to_dot(
|
|||||||
|
|
||||||
cfg.traverse(emit, start=cfg.START)
|
cfg.traverse(emit, start=cfg.START)
|
||||||
|
|
||||||
|
# Draw dashed def -> use edges for Reached Uses
|
||||||
if ru_edges:
|
if ru_edges:
|
||||||
for idx, def_id in enumerate(sorted(ru_edges)):
|
for idx, def_id in enumerate(sorted(ru_edges)):
|
||||||
use_ids = sorted(set(ru_edges[def_id]))
|
use_ids = sorted(set(ru_edges[def_id]))
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ import cfg_build
|
|||||||
import lib.console as cnsl
|
import lib.console as cnsl
|
||||||
import syntax
|
import syntax
|
||||||
import triplayacc as yacc
|
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 cfg.CFG import CFG
|
||||||
from vistram.tram import *
|
from vistram.tram import *
|
||||||
from vistram.vistram import MachineUI
|
from vistram.vistram import MachineUI
|
||||||
@@ -64,12 +66,11 @@ def pretty_print(node, indent=0):
|
|||||||
else:
|
else:
|
||||||
print(f"{prefix} {key}: {value}")
|
print(f"{prefix} {key}: {value}")
|
||||||
|
|
||||||
|
# Print compact Live Variables and Reached Uses reports to console.
|
||||||
def print_analysis_reports(cfg, analyses: dict, ru_edges: dict[int, list[int]]):
|
def print_analysis_reports(cfg, analyses: dict):
|
||||||
"""Print compact Live Variables and Reached Uses reports to console."""
|
|
||||||
lv = analyses["lv"]
|
lv = analyses["lv"]
|
||||||
ru = analyses["ru"]
|
ru = analyses["ru"]
|
||||||
_ = ru
|
ru_edges = ru.reached_uses_by_node()
|
||||||
node_by_id = {n.id: n for n in cfg.nodes()}
|
node_by_id = {n.id: n for n in cfg.nodes()}
|
||||||
|
|
||||||
def node_text(nid: int) -> str:
|
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:
|
if not has_ru:
|
||||||
print("(no reached uses)")
|
print("(no reached uses)")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
print("\nTRIPLA Parser and TRIPLA to TRAM Compiler")
|
print("\nTRIPLA Parser and TRIPLA to TRAM Compiler")
|
||||||
|
|
||||||
@@ -211,28 +211,25 @@ if __name__ == "__main__":
|
|||||||
print("Rendered CFG diagram.")
|
print("Rendered CFG diagram.")
|
||||||
|
|
||||||
elif mode == 3:
|
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.FUNCTIONS.clear()
|
||||||
cfg_build.CURRENT_FUNCTION = None
|
cfg_build.CURRENT_FUNCTION = None
|
||||||
|
|
||||||
cfg = make_cfg(ast)
|
cfg = make_cfg(ast)
|
||||||
analysis_name = "Live Variables + Reached Uses"
|
analysis_name = "Live Variables + Reached Uses analyses"
|
||||||
print(f"\nRunning {analysis_name} …")
|
print(f"\nRunning {analysis_name} …")
|
||||||
try:
|
try:
|
||||||
_analyses, annotations, ru_edges = run_all_analyses(cfg)
|
lv = LiveVariables(cfg)
|
||||||
|
ru = ReachedUses(cfg)
|
||||||
|
analyses = {"lv": lv, "ru": ru}
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
print(f"Analysis failed: {exc}")
|
print(f"Analysis failed: {exc}")
|
||||||
continue
|
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?"):
|
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?"):
|
if cnsl.prompt_confirmation("\nExport annotated CFG as .dot file?"):
|
||||||
default = f"{path.stem}_analysis.dot"
|
default = f"{path.stem}_analysis.dot"
|
||||||
|
|||||||
Reference in New Issue
Block a user