blob: 17c8597740d9b0f7a20434acf7783bbffd2b09ce [file] [log] [blame] [edit]
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::io::{self, Write};
use crate::node::*;
#[derive(Clone, Serialize, Deserialize)]
pub enum GraphKind {
Digraph,
Subgraph,
}
pub type AdjList<'a> = HashMap<&'a str, Vec<&'a str>>;
/// Graph represents a directed graph as a list of nodes and list of edges.
#[derive(Serialize, Deserialize)]
pub struct Graph {
/// Identifier for the graph
pub name: String,
/// The Vector containing the Nodes
pub nodes: Vec<Node>,
/// The Vector containing the Edges
pub edges: Vec<Edge>,
}
#[derive(Clone)]
pub struct GraphvizSettings {
/// The attributes of the graph in graphviz.
pub graph_attrs: Option<String>,
/// The attributes of the nodes in graphviz.
pub node_attrs: Option<String>,
/// The attributes of the edges in graphviz.
pub edge_attrs: Option<String>,
/// Label of the graph
pub graph_label: Option<String>,
}
impl Default for GraphvizSettings {
fn default() -> GraphvizSettings {
GraphvizSettings {
graph_attrs: None,
node_attrs: None,
edge_attrs: None,
graph_label: None,
}
}
}
impl Graph {
pub fn new(name: String, nodes: Vec<Node>, edges: Vec<Edge>) -> Graph {
Graph { name, nodes, edges }
}
/// Returns the adjacency list representation of the graph.
/// Adjacency list can be used to easily find the childern of a given node.
/// If the a node does not have any childern, then the list correspoding to that node
/// will be empty.
pub fn adj_list(&self) -> AdjList<'_> {
let mut m: AdjList<'_> = HashMap::new();
for node in &self.nodes {
m.insert(&node.label, Vec::new());
}
for edge in &self.edges {
m.entry(&edge.from).or_insert_with(Vec::new).push(&edge.to);
}
m
}
/// Returns the reverse adjacency list representation of the graph.
/// Reverse adjacency list represents the adjacency list of a directed graph where
/// the edges have been reversed.
/// Reverse adjacency list can be used to easily find the parents of a given node.
/// If the a node does not have any childern, then the list correspoding to that node
/// will be empty.
pub fn rev_adj_list(&self) -> AdjList<'_> {
let mut m: AdjList<'_> = HashMap::new();
for node in &self.nodes {
m.insert(&node.label, Vec::new());
}
for edge in &self.edges {
m.entry(&edge.to).or_insert_with(Vec::new).push(&edge.from);
}
m
}
/// Returns the node with the given label, if found.
pub fn get_node_by_label(&self, label: &str) -> Option<&Node> {
self.nodes.iter().find(|node| node.label == *label)
}
/// Returns the dot representation of the given graph.
/// This can rendered using the graphviz program.
pub fn to_dot<W: Write>(
&self,
w: &mut W,
settings: &GraphvizSettings,
as_subgraph: bool,
) -> io::Result<()> {
if as_subgraph {
write!(w, "subgraph cluster_{}", self.name)?;
} else {
write!(w, "digraph {}", self.name)?;
}
writeln!(w, " {{")?;
if let Some(graph_attrs) = &settings.graph_attrs {
writeln!(w, r#" graph [{}];"#, graph_attrs)?;
}
if let Some(node_attrs) = &settings.node_attrs {
writeln!(w, r#" node [{}];"#, node_attrs)?;
}
if let Some(edge_attrs) = &settings.edge_attrs {
writeln!(w, r#" edge [{}];"#, edge_attrs)?;
}
if let Some(label) = &settings.graph_label {
writeln!(w, r#" label=<{}>;"#, label)?;
}
for node in self.nodes.iter() {
write!(w, r#" {} [shape="none", label=<"#, node.label)?;
node.to_dot(w)?;
writeln!(w, ">];")?;
}
for edge in self.edges.iter() {
edge.to_dot(w)?;
}
writeln!(w, "}}")
}
}
#[cfg(test)]
mod tests {
use crate::*;
fn get_test_graph() -> Graph {
let stmts: Vec<String> = vec!["hi".into(), "hell".into()];
let label1: String = "bb0__0_3".into();
let style: NodeStyle = Default::default();
let node1 = Node::new(stmts, label1.clone(), "0".into(), style.clone());
let stmts: Vec<String> = vec!["_1 = const 1_i32".into(), "_2 = const 2_i32".into()];
let label2: String = "bb0__1_3".into();
let node2 = Node::new(stmts, label2.clone(), "1".into(), style.clone());
Graph::new(
"Mir_0_3".into(),
vec![node1, node2],
vec![Edge::new(label1, label2, "return".into())],
)
}
#[test]
fn test_adj_list() {
let g = get_test_graph();
let adj_list = g.adj_list();
let expected: AdjList = [("bb0__0_3", vec!["bb0__1_3"]), (&"bb0__1_3", vec![])]
.iter()
.cloned()
.collect();
assert_eq!(adj_list, expected);
}
#[test]
fn test_json_ser() {
let g = get_test_graph();
let json = serde_json::to_string(&g).unwrap();
let expected_json: String = "\
{\
\"name\":\"Mir_0_3\",\
\"nodes\":[\
{\
\"stmts\":[\
\"hi\",\
\"hell\"\
],\
\"label\":\"bb0__0_3\",\
\"title\":\"0\",\
\"style\":{\
\"title_bg\":null,\
\"last_stmt_sep\":false\
}\
},\
{\
\"stmts\":[\
\"_1 = const 1_i32\",\
\"_2 = const 2_i32\"\
],\
\"label\":\"bb0__1_3\",\
\"title\":\"1\",\
\"style\":{\
\"title_bg\":null,\
\"last_stmt_sep\":false\
}\
}\
],\
\"edges\":[\
{\
\"from\":\"bb0__0_3\",\
\"to\":\"bb0__1_3\",\
\"label\":\"return\"\
}\
]\
}"
.into();
assert_eq!(json, expected_json)
}
#[test]
fn test_json_deser() {
let expected = get_test_graph();
let struct_json: String = "\
{\
\"name\":\"Mir_0_3\",\
\"nodes\":[\
{\
\"stmts\":[\
\"hi\",\
\"hell\"\
],\
\"label\":\"bb0__0_3\",\
\"title\":\"0\",\
\"style\":{\
\"title_bg\":null,\
\"last_stmt_sep\":false\
}\
},\
{\
\"stmts\":[\
\"_1 = const 1_i32\",\
\"_2 = const 2_i32\"\
],\
\"label\":\"bb0__1_3\",\
\"title\":\"1\",\
\"style\":{\
\"title_bg\":null,\
\"last_stmt_sep\":false\
}\
}\
],\
\"edges\":[\
{\
\"from\":\"bb0__0_3\",\
\"to\":\"bb0__1_3\",\
\"label\":\"return\"\
}\
]\
}"
.into();
let got: Graph = serde_json::from_str(&struct_json).unwrap();
assert_eq!(expected.nodes.len(), got.nodes.len());
assert_eq!(expected.edges.len(), got.edges.len());
for (n1, n2) in expected.nodes.iter().zip(got.nodes.iter()) {
assert_eq!(n1.stmts, n2.stmts);
assert_eq!(n1.label, n2.label);
assert_eq!(n1.title, n2.title);
}
for (e1, e2) in expected.edges.iter().zip(got.edges.iter()) {
assert_eq!(e1.from, e2.from);
assert_eq!(e1.to, e2.to);
assert_eq!(e1.label, e2.label);
}
}
}