package ch.usi.inf.sp.cfg; import java.util.stream.Collectors; import java.util.stream.StreamSupport; public class ControlFlowGraphRenderer { private static final String INDENTATION = " "; private final StringBuilder code = new StringBuilder(); private int indentationLevel = 0; private static String nodeIdentifier(final BasicBlock bb, final String graphId) { // as the basic block ID is negative for the entry and exit node, and negative numbers contain // a dash symbol, we need to quote the identifier to make a syntactically correct DOT file return String.format("\"%sbb%d\"", graphId, bb.getId()); } private static String nodeStyle(final BasicBlock bb, final String label) { if (bb.getInEdges().isEmpty()) { return "[shape=circle,label=\"e\",xlabel=\"" + label + "\"]"; } else if (bb.getOutEdges().isEmpty()) { return "[shape=circle,label=\"x\"]"; } else { return "[label=\"" + bb.getId() + "|{" + StreamSupport.stream(bb.getInstructions().spliterator(), false) .collect(Collectors.joining("|")) + "}\"]"; } } public static String renderControlFlowGraph(final String label, final ControlFlowGraph cfg) { return new ControlFlowGraphRenderer().render(label, cfg); } private void line(String line) { code.append(INDENTATION.repeat(indentationLevel)).append(line).append('\n'); } private String render(final String desiredLabel, final ControlFlowGraph graph) { final String label = desiredLabel.replaceAll("\\W+", ""); line("digraph " + label + " {"); code.append('\n'); indentationLevel++; line("node [shape=record]"); for (final BasicBlock bb : graph.getNodes()) { line(nodeIdentifier(bb, label) + " " + nodeStyle(bb, label)); } code.append('\n'); for (var e : graph.getEdges()) { final String l = e.getLabel(); final String suffix = l == null || l.isBlank() ? "" : (" [label=\"" + e.getLabel() + "\"]"); line(nodeIdentifier(e.getFrom(), label) + " -> " + nodeIdentifier(e.getTo(), label) + suffix); } indentationLevel--; line("}"); return code.toString(); } }