| 1 | use serde::{Deserialize, Serialize}; | 
| 2 | use std::collections::HashMap; | 
|---|
| 3 | use std::io::{self, Write}; | 
|---|
| 4 |  | 
|---|
| 5 | use crate::node::*; | 
|---|
| 6 |  | 
|---|
| 7 | #[ derive(Clone, Serialize, Deserialize)] | 
|---|
| 8 | pub enum GraphKind { | 
|---|
| 9 | Digraph, | 
|---|
| 10 | Subgraph, | 
|---|
| 11 | } | 
|---|
| 12 |  | 
|---|
| 13 | pub type AdjList<'a> = HashMap<&'a str, Vec<&'a str>>; | 
|---|
| 14 |  | 
|---|
| 15 | /// Graph represents a directed graph as a list of nodes and list of edges. | 
|---|
| 16 | #[ derive(Serialize, Deserialize)] | 
|---|
| 17 | pub struct Graph { | 
|---|
| 18 | /// Identifier for the graph | 
|---|
| 19 | pub name: String, | 
|---|
| 20 |  | 
|---|
| 21 | /// The Vector containing the Nodes | 
|---|
| 22 | pub nodes: Vec<Node>, | 
|---|
| 23 |  | 
|---|
| 24 | /// The Vector containing the Edges | 
|---|
| 25 | pub edges: Vec<Edge>, | 
|---|
| 26 | } | 
|---|
| 27 |  | 
|---|
| 28 | #[ derive(Clone)] | 
|---|
| 29 | pub struct GraphvizSettings { | 
|---|
| 30 | /// The attributes of the graph in graphviz. | 
|---|
| 31 | pub graph_attrs: Option<String>, | 
|---|
| 32 |  | 
|---|
| 33 | /// The attributes of the nodes in graphviz. | 
|---|
| 34 | pub node_attrs: Option<String>, | 
|---|
| 35 |  | 
|---|
| 36 | /// The attributes of the edges in graphviz. | 
|---|
| 37 | pub edge_attrs: Option<String>, | 
|---|
| 38 |  | 
|---|
| 39 | /// Label of the graph | 
|---|
| 40 | pub graph_label: Option<String>, | 
|---|
| 41 | } | 
|---|
| 42 |  | 
|---|
| 43 | impl Default for GraphvizSettings { | 
|---|
| 44 | fn default() -> GraphvizSettings { | 
|---|
| 45 | GraphvizSettings { | 
|---|
| 46 | graph_attrs: None, | 
|---|
| 47 | node_attrs: None, | 
|---|
| 48 | edge_attrs: None, | 
|---|
| 49 | graph_label: None, | 
|---|
| 50 | } | 
|---|
| 51 | } | 
|---|
| 52 | } | 
|---|
| 53 |  | 
|---|
| 54 | impl Graph { | 
|---|
| 55 | pub fn new(name: String, nodes: Vec<Node>, edges: Vec<Edge>) -> Graph { | 
|---|
| 56 | Graph { name, nodes, edges } | 
|---|
| 57 | } | 
|---|
| 58 |  | 
|---|
| 59 | /// Returns the adjacency list representation of the graph. | 
|---|
| 60 | /// Adjacency list can be used to easily find the childern of a given node. | 
|---|
| 61 | /// If the a node does not have any childern, then the list correspoding to that node | 
|---|
| 62 | /// will be empty. | 
|---|
| 63 | pub fn adj_list(&self) -> AdjList<'_> { | 
|---|
| 64 | let mut m: AdjList<'_> = HashMap::new(); | 
|---|
| 65 | for node in &self.nodes { | 
|---|
| 66 | m.insert(&node.label, Vec::new()); | 
|---|
| 67 | } | 
|---|
| 68 | for edge in &self.edges { | 
|---|
| 69 | m.entry(&edge.from).or_insert_with(Vec::new).push(&edge.to); | 
|---|
| 70 | } | 
|---|
| 71 | m | 
|---|
| 72 | } | 
|---|
| 73 |  | 
|---|
| 74 | /// Returns the reverse adjacency list representation of the graph. | 
|---|
| 75 | /// Reverse adjacency list represents the adjacency list of a directed graph where | 
|---|
| 76 | /// the edges have been reversed. | 
|---|
| 77 | /// Reverse adjacency list can be used to easily find the parents of a given node. | 
|---|
| 78 | /// If the a node does not have any childern, then the list correspoding to that node | 
|---|
| 79 | /// will be empty. | 
|---|
| 80 | pub fn rev_adj_list(&self) -> AdjList<'_> { | 
|---|
| 81 | let mut m: AdjList<'_> = HashMap::new(); | 
|---|
| 82 | for node in &self.nodes { | 
|---|
| 83 | m.insert(&node.label, Vec::new()); | 
|---|
| 84 | } | 
|---|
| 85 | for edge in &self.edges { | 
|---|
| 86 | m.entry(&edge.to).or_insert_with(Vec::new).push(&edge.from); | 
|---|
| 87 | } | 
|---|
| 88 | m | 
|---|
| 89 | } | 
|---|
| 90 |  | 
|---|
| 91 | /// Returns the node with the given label, if found. | 
|---|
| 92 | pub fn get_node_by_label(&self, label: &str) -> Option<&Node> { | 
|---|
| 93 | self.nodes.iter().find(|node| node.label == *label) | 
|---|
| 94 | } | 
|---|
| 95 |  | 
|---|
| 96 | /// Returns the dot representation of the given graph. | 
|---|
| 97 | /// This can rendered using the graphviz program. | 
|---|
| 98 | pub fn to_dot<W: Write>( | 
|---|
| 99 | &self, | 
|---|
| 100 | w: &mut W, | 
|---|
| 101 | settings: &GraphvizSettings, | 
|---|
| 102 | as_subgraph: bool, | 
|---|
| 103 | ) -> io::Result<()> { | 
|---|
| 104 | if as_subgraph { | 
|---|
| 105 | write!(w, "subgraph cluster_{} ", self.name)?; | 
|---|
| 106 | } else { | 
|---|
| 107 | write!(w, "digraph {} ", self.name)?; | 
|---|
| 108 | } | 
|---|
| 109 |  | 
|---|
| 110 | writeln!(w, " {{ ")?; | 
|---|
| 111 |  | 
|---|
| 112 | if let Some(graph_attrs) = &settings.graph_attrs { | 
|---|
| 113 | writeln!(w, r#"    graph [{} ];"#, graph_attrs)?; | 
|---|
| 114 | } | 
|---|
| 115 | if let Some(node_attrs) = &settings.node_attrs { | 
|---|
| 116 | writeln!(w, r#"    node [{} ];"#, node_attrs)?; | 
|---|
| 117 | } | 
|---|
| 118 | if let Some(edge_attrs) = &settings.edge_attrs { | 
|---|
| 119 | writeln!(w, r#"    edge [{} ];"#, edge_attrs)?; | 
|---|
| 120 | } | 
|---|
| 121 | if let Some(label) = &settings.graph_label { | 
|---|
| 122 | writeln!(w, r#"    label=<{} >;"#, label)?; | 
|---|
| 123 | } | 
|---|
| 124 |  | 
|---|
| 125 | for node in self.nodes.iter() { | 
|---|
| 126 | write!(w, r#"    {}  [shape="none", label=<"#, node.label)?; | 
|---|
| 127 | node.to_dot(w)?; | 
|---|
| 128 | writeln!(w, ">];")?; | 
|---|
| 129 | } | 
|---|
| 130 |  | 
|---|
| 131 | for edge in self.edges.iter() { | 
|---|
| 132 | edge.to_dot(w)?; | 
|---|
| 133 | } | 
|---|
| 134 |  | 
|---|
| 135 | writeln!(w, "}} ") | 
|---|
| 136 | } | 
|---|
| 137 | } | 
|---|
| 138 |  | 
|---|
| 139 | #[ cfg(test)] | 
|---|
| 140 | mod tests { | 
|---|
| 141 | use crate::*; | 
|---|
| 142 | fn get_test_graph() -> Graph { | 
|---|
| 143 | let stmts: Vec<String> = vec![ "hi".into(), "hell".into()]; | 
|---|
| 144 | let label1: String = "bb0__0_3".into(); | 
|---|
| 145 | let style: NodeStyle = Default::default(); | 
|---|
| 146 | let node1 = Node::new(stmts, label1.clone(), "0".into(), style.clone()); | 
|---|
| 147 |  | 
|---|
| 148 | let stmts: Vec<String> = vec![ "_1 = const 1_i32".into(), "_2 = const 2_i32".into()]; | 
|---|
| 149 | let label2: String = "bb0__1_3".into(); | 
|---|
| 150 | let node2 = Node::new(stmts, label2.clone(), "1".into(), style.clone()); | 
|---|
| 151 |  | 
|---|
| 152 | Graph::new( | 
|---|
| 153 | "Mir_0_3".into(), | 
|---|
| 154 | vec![node1, node2], | 
|---|
| 155 | vec![Edge::new(label1, label2, "return".into())], | 
|---|
| 156 | ) | 
|---|
| 157 | } | 
|---|
| 158 |  | 
|---|
| 159 | #[ test] | 
|---|
| 160 | fn test_adj_list() { | 
|---|
| 161 | let g = get_test_graph(); | 
|---|
| 162 | let adj_list = g.adj_list(); | 
|---|
| 163 | let expected: AdjList = [( "bb0__0_3", vec![ "bb0__1_3"]), (& "bb0__1_3", vec![])] | 
|---|
| 164 | .iter() | 
|---|
| 165 | .cloned() | 
|---|
| 166 | .collect(); | 
|---|
| 167 | assert_eq!(adj_list, expected); | 
|---|
| 168 | } | 
|---|
| 169 |  | 
|---|
| 170 | #[ test] | 
|---|
| 171 | fn test_json_ser() { | 
|---|
| 172 | let g = get_test_graph(); | 
|---|
| 173 | let json = serde_json::to_string(&g).unwrap(); | 
|---|
| 174 | let expected_json: String = "\ | 
|---|
| 175 |         {\ | 
|---|
| 176 |             \" name\" :\" Mir_0_3\" ,\ | 
|---|
| 177 |             \" nodes\" :[\ | 
|---|
| 178 |             {\ | 
|---|
| 179 |                 \" stmts\" :[\ | 
|---|
| 180 |                     \" hi\" ,\ | 
|---|
| 181 |                     \" hell\" \ | 
|---|
| 182 |                 ],\ | 
|---|
| 183 |                 \" label\" :\" bb0__0_3\" ,\ | 
|---|
| 184 |                 \" title\" :\" 0\" ,\ | 
|---|
| 185 |                 \" style\" :{\ | 
|---|
| 186 |                     \" title_bg\" :null,\ | 
|---|
| 187 |                     \" last_stmt_sep\" :false\ | 
|---|
| 188 |                 }\ | 
|---|
| 189 |             },\ | 
|---|
| 190 |             {\ | 
|---|
| 191 |                 \" stmts\" :[\ | 
|---|
| 192 |                     \" _1 = const 1_i32\" ,\ | 
|---|
| 193 |                     \" _2 = const 2_i32\" \ | 
|---|
| 194 |                 ],\ | 
|---|
| 195 |                 \" label\" :\" bb0__1_3\" ,\ | 
|---|
| 196 |                 \" title\" :\" 1\" ,\ | 
|---|
| 197 |                 \" style\" :{\ | 
|---|
| 198 |                     \" title_bg\" :null,\ | 
|---|
| 199 |                     \" last_stmt_sep\" :false\ | 
|---|
| 200 |                 }\ | 
|---|
| 201 |             }\ | 
|---|
| 202 |             ],\ | 
|---|
| 203 |             \" edges\" :[\ | 
|---|
| 204 |             {\ | 
|---|
| 205 |                 \" from\" :\" bb0__0_3\" ,\ | 
|---|
| 206 |                 \" to\" :\" bb0__1_3\" ,\ | 
|---|
| 207 |                 \" label\" :\" return\" \ | 
|---|
| 208 |             }\ | 
|---|
| 209 |             ]\ | 
|---|
| 210 |         }" | 
|---|
| 211 | .into(); | 
|---|
| 212 | assert_eq!(json, expected_json) | 
|---|
| 213 | } | 
|---|
| 214 |  | 
|---|
| 215 | #[ test] | 
|---|
| 216 | fn test_json_deser() { | 
|---|
| 217 | let expected = get_test_graph(); | 
|---|
| 218 | let struct_json: String = "\ | 
|---|
| 219 |         {\ | 
|---|
| 220 |             \" name\" :\" Mir_0_3\" ,\ | 
|---|
| 221 |             \" nodes\" :[\ | 
|---|
| 222 |             {\ | 
|---|
| 223 |                 \" stmts\" :[\ | 
|---|
| 224 |                     \" hi\" ,\ | 
|---|
| 225 |                     \" hell\" \ | 
|---|
| 226 |                 ],\ | 
|---|
| 227 |                 \" label\" :\" bb0__0_3\" ,\ | 
|---|
| 228 |                 \" title\" :\" 0\" ,\ | 
|---|
| 229 |                 \" style\" :{\ | 
|---|
| 230 |                     \" title_bg\" :null,\ | 
|---|
| 231 |                     \" last_stmt_sep\" :false\ | 
|---|
| 232 |                 }\ | 
|---|
| 233 |             },\ | 
|---|
| 234 |             {\ | 
|---|
| 235 |                 \" stmts\" :[\ | 
|---|
| 236 |                     \" _1 = const 1_i32\" ,\ | 
|---|
| 237 |                     \" _2 = const 2_i32\" \ | 
|---|
| 238 |                 ],\ | 
|---|
| 239 |                 \" label\" :\" bb0__1_3\" ,\ | 
|---|
| 240 |                 \" title\" :\" 1\" ,\ | 
|---|
| 241 |                 \" style\" :{\ | 
|---|
| 242 |                     \" title_bg\" :null,\ | 
|---|
| 243 |                     \" last_stmt_sep\" :false\ | 
|---|
| 244 |                 }\ | 
|---|
| 245 |             }\ | 
|---|
| 246 |             ],\ | 
|---|
| 247 |             \" edges\" :[\ | 
|---|
| 248 |             {\ | 
|---|
| 249 |                 \" from\" :\" bb0__0_3\" ,\ | 
|---|
| 250 |                 \" to\" :\" bb0__1_3\" ,\ | 
|---|
| 251 |                 \" label\" :\" return\" \ | 
|---|
| 252 |             }\ | 
|---|
| 253 |             ]\ | 
|---|
| 254 |         }" | 
|---|
| 255 | .into(); | 
|---|
| 256 | let got: Graph = serde_json::from_str(&struct_json).unwrap(); | 
|---|
| 257 |  | 
|---|
| 258 | assert_eq!(expected.nodes.len(), got.nodes.len()); | 
|---|
| 259 | assert_eq!(expected.edges.len(), got.edges.len()); | 
|---|
| 260 |  | 
|---|
| 261 | for (n1, n2) in expected.nodes.iter().zip(got.nodes.iter()) { | 
|---|
| 262 | assert_eq!(n1.stmts, n2.stmts); | 
|---|
| 263 | assert_eq!(n1.label, n2.label); | 
|---|
| 264 | assert_eq!(n1.title, n2.title); | 
|---|
| 265 | } | 
|---|
| 266 |  | 
|---|
| 267 | for (e1, e2) in expected.edges.iter().zip(got.edges.iter()) { | 
|---|
| 268 | assert_eq!(e1.from, e2.from); | 
|---|
| 269 | assert_eq!(e1.to, e2.to); | 
|---|
| 270 | assert_eq!(e1.label, e2.label); | 
|---|
| 271 | } | 
|---|
| 272 | } | 
|---|
| 273 | } | 
|---|
| 274 |  | 
|---|