| use std::io; | |
| use std::io::prelude::*; | |
| use NodeLabels::*; | |
| use super::LabelText::{self, EscStr, HtmlStr, LabelStr}; | |
| use super::{Edges, GraphWalk, Id, Labeller, Nodes, Style, render}; | |
| /// each node is an index in a vector in the graph. | |
| type Node = usize; | |
| struct Edge { | |
| from: usize, | |
| to: usize, | |
| label: &'static str, | |
| style: Style, | |
| } | |
| fn edge(from: usize, to: usize, label: &'static str, style: Style) -> Edge { | |
| Edge { from, to, label, style } | |
| } | |
| struct LabelledGraph { | |
| /// The name for this graph. Used for labeling generated `digraph`. | |
| name: &'static str, | |
| /// Each node is an index into `node_labels`; these labels are | |
| /// used as the label text for each node. (The node *names*, | |
| /// which are unique identifiers, are derived from their index | |
| /// in this array.) | |
| /// | |
| /// If a node maps to None here, then just use its name as its | |
| /// text. | |
| node_labels: Vec<Option<&'static str>>, | |
| node_styles: Vec<Style>, | |
| /// Each edge relates a from-index to a to-index along with a | |
| /// label; `edges` collects them. | |
| edges: Vec<Edge>, | |
| } | |
| // A simple wrapper around LabelledGraph that forces the labels to | |
| // be emitted as EscStr. | |
| struct LabelledGraphWithEscStrs { | |
| graph: LabelledGraph, | |
| } | |
| enum NodeLabels<L> { | |
| AllNodesLabelled(Vec<L>), | |
| UnlabelledNodes(usize), | |
| SomeNodesLabelled(Vec<Option<L>>), | |
| } | |
| type Trivial = NodeLabels<&'static str>; | |
| impl NodeLabels<&'static str> { | |
| fn to_opt_strs(self) -> Vec<Option<&'static str>> { | |
| match self { | |
| UnlabelledNodes(len) => vec![None; len], | |
| AllNodesLabelled(lbls) => lbls.into_iter().map(Some).collect(), | |
| SomeNodesLabelled(lbls) => lbls, | |
| } | |
| } | |
| fn len(&self) -> usize { | |
| match *self { | |
| UnlabelledNodes(len) => len, | |
| AllNodesLabelled(ref lbls) => lbls.len(), | |
| SomeNodesLabelled(ref lbls) => lbls.len(), | |
| } | |
| } | |
| } | |
| impl LabelledGraph { | |
| fn new( | |
| name: &'static str, | |
| node_labels: Trivial, | |
| edges: Vec<Edge>, | |
| node_styles: Option<Vec<Style>>, | |
| ) -> LabelledGraph { | |
| let count = node_labels.len(); | |
| LabelledGraph { | |
| name, | |
| node_labels: node_labels.to_opt_strs(), | |
| edges, | |
| node_styles: match node_styles { | |
| Some(nodes) => nodes, | |
| None => vec![Style::None; count], | |
| }, | |
| } | |
| } | |
| } | |
| impl LabelledGraphWithEscStrs { | |
| fn new(name: &'static str, node_labels: Trivial, edges: Vec<Edge>) -> LabelledGraphWithEscStrs { | |
| LabelledGraphWithEscStrs { graph: LabelledGraph::new(name, node_labels, edges, None) } | |
| } | |
| } | |
| fn id_name<'a>(n: &Node) -> Id<'a> { | |
| Id::new(format!("N{}", *n)).unwrap() | |
| } | |
| impl<'a> Labeller<'a> for LabelledGraph { | |
| type Node = Node; | |
| type Edge = &'a Edge; | |
| fn graph_id(&'a self) -> Id<'a> { | |
| Id::new(self.name).unwrap() | |
| } | |
| fn node_id(&'a self, n: &Node) -> Id<'a> { | |
| id_name(n) | |
| } | |
| fn node_label(&'a self, n: &Node) -> LabelText<'a> { | |
| match self.node_labels[*n] { | |
| Some(l) => LabelStr(l.into()), | |
| None => LabelStr(id_name(n).name), | |
| } | |
| } | |
| fn edge_label(&'a self, e: &&'a Edge) -> LabelText<'a> { | |
| LabelStr(e.label.into()) | |
| } | |
| fn node_style(&'a self, n: &Node) -> Style { | |
| self.node_styles[*n] | |
| } | |
| fn edge_style(&'a self, e: &&'a Edge) -> Style { | |
| e.style | |
| } | |
| } | |
| impl<'a> Labeller<'a> for LabelledGraphWithEscStrs { | |
| type Node = Node; | |
| type Edge = &'a Edge; | |
| fn graph_id(&'a self) -> Id<'a> { | |
| self.graph.graph_id() | |
| } | |
| fn node_id(&'a self, n: &Node) -> Id<'a> { | |
| self.graph.node_id(n) | |
| } | |
| fn node_label(&'a self, n: &Node) -> LabelText<'a> { | |
| match self.graph.node_label(n) { | |
| LabelStr(s) | EscStr(s) | HtmlStr(s) => EscStr(s), | |
| } | |
| } | |
| fn edge_label(&'a self, e: &&'a Edge) -> LabelText<'a> { | |
| match self.graph.edge_label(e) { | |
| LabelStr(s) | EscStr(s) | HtmlStr(s) => EscStr(s), | |
| } | |
| } | |
| } | |
| impl<'a> GraphWalk<'a> for LabelledGraph { | |
| type Node = Node; | |
| type Edge = &'a Edge; | |
| fn nodes(&'a self) -> Nodes<'a, Node> { | |
| (0..self.node_labels.len()).collect() | |
| } | |
| fn edges(&'a self) -> Edges<'a, &'a Edge> { | |
| self.edges.iter().collect() | |
| } | |
| fn source(&'a self, edge: &&'a Edge) -> Node { | |
| edge.from | |
| } | |
| fn target(&'a self, edge: &&'a Edge) -> Node { | |
| edge.to | |
| } | |
| } | |
| impl<'a> GraphWalk<'a> for LabelledGraphWithEscStrs { | |
| type Node = Node; | |
| type Edge = &'a Edge; | |
| fn nodes(&'a self) -> Nodes<'a, Node> { | |
| self.graph.nodes() | |
| } | |
| fn edges(&'a self) -> Edges<'a, &'a Edge> { | |
| self.graph.edges() | |
| } | |
| fn source(&'a self, edge: &&'a Edge) -> Node { | |
| edge.from | |
| } | |
| fn target(&'a self, edge: &&'a Edge) -> Node { | |
| edge.to | |
| } | |
| } | |
| fn test_input(g: LabelledGraph) -> io::Result<String> { | |
| let mut writer = Vec::new(); | |
| render(&g, &mut writer).unwrap(); | |
| let mut s = String::new(); | |
| Read::read_to_string(&mut &*writer, &mut s)?; | |
| Ok(s) | |
| } | |
| // All of the tests use raw-strings as the format for the expected outputs, | |
| // so that you can cut-and-paste the content into a .dot file yourself to | |
| // see what the graphviz visualizer would produce. | |
| #[test] | |
| fn empty_graph() { | |
| let labels: Trivial = UnlabelledNodes(0); | |
| let r = test_input(LabelledGraph::new("empty_graph", labels, vec![], None)); | |
| assert_eq!( | |
| r.unwrap(), | |
| r#"digraph empty_graph { | |
| } | |
| "# | |
| ); | |
| } | |
| #[test] | |
| fn single_node() { | |
| let labels: Trivial = UnlabelledNodes(1); | |
| let r = test_input(LabelledGraph::new("single_node", labels, vec![], None)); | |
| assert_eq!( | |
| r.unwrap(), | |
| r#"digraph single_node { | |
| N0[label="N0"]; | |
| } | |
| "# | |
| ); | |
| } | |
| #[test] | |
| fn single_node_with_style() { | |
| let labels: Trivial = UnlabelledNodes(1); | |
| let styles = Some(vec![Style::Dashed]); | |
| let r = test_input(LabelledGraph::new("single_node", labels, vec![], styles)); | |
| assert_eq!( | |
| r.unwrap(), | |
| r#"digraph single_node { | |
| N0[label="N0"][style="dashed"]; | |
| } | |
| "# | |
| ); | |
| } | |
| #[test] | |
| fn single_edge() { | |
| let labels: Trivial = UnlabelledNodes(2); | |
| let result = test_input(LabelledGraph::new( | |
| "single_edge", | |
| labels, | |
| vec![edge(0, 1, "E", Style::None)], | |
| None, | |
| )); | |
| assert_eq!( | |
| result.unwrap(), | |
| r#"digraph single_edge { | |
| N0[label="N0"]; | |
| N1[label="N1"]; | |
| N0 -> N1[label="E"]; | |
| } | |
| "# | |
| ); | |
| } | |
| #[test] | |
| fn single_edge_with_style() { | |
| let labels: Trivial = UnlabelledNodes(2); | |
| let result = test_input(LabelledGraph::new( | |
| "single_edge", | |
| labels, | |
| vec![edge(0, 1, "E", Style::Bold)], | |
| None, | |
| )); | |
| assert_eq!( | |
| result.unwrap(), | |
| r#"digraph single_edge { | |
| N0[label="N0"]; | |
| N1[label="N1"]; | |
| N0 -> N1[label="E"][style="bold"]; | |
| } | |
| "# | |
| ); | |
| } | |
| #[test] | |
| fn test_some_labelled() { | |
| let labels: Trivial = SomeNodesLabelled(vec![Some("A"), None]); | |
| let styles = Some(vec![Style::None, Style::Dotted]); | |
| let result = test_input(LabelledGraph::new( | |
| "test_some_labelled", | |
| labels, | |
| vec![edge(0, 1, "A-1", Style::None)], | |
| styles, | |
| )); | |
| assert_eq!( | |
| result.unwrap(), | |
| r#"digraph test_some_labelled { | |
| N0[label="A"]; | |
| N1[label="N1"][style="dotted"]; | |
| N0 -> N1[label="A-1"]; | |
| } | |
| "# | |
| ); | |
| } | |
| #[test] | |
| fn single_cyclic_node() { | |
| let labels: Trivial = UnlabelledNodes(1); | |
| let r = test_input(LabelledGraph::new( | |
| "single_cyclic_node", | |
| labels, | |
| vec![edge(0, 0, "E", Style::None)], | |
| None, | |
| )); | |
| assert_eq!( | |
| r.unwrap(), | |
| r#"digraph single_cyclic_node { | |
| N0[label="N0"]; | |
| N0 -> N0[label="E"]; | |
| } | |
| "# | |
| ); | |
| } | |
| #[test] | |
| fn hasse_diagram() { | |
| let labels = AllNodesLabelled(vec!["{x,y}", "{x}", "{y}", "{}"]); | |
| let r = test_input(LabelledGraph::new( | |
| "hasse_diagram", | |
| labels, | |
| vec![ | |
| edge(0, 1, "", Style::None), | |
| edge(0, 2, "", Style::None), | |
| edge(1, 3, "", Style::None), | |
| edge(2, 3, "", Style::None), | |
| ], | |
| None, | |
| )); | |
| assert_eq!( | |
| r.unwrap(), | |
| r#"digraph hasse_diagram { | |
| N0[label="{x,y}"]; | |
| N1[label="{x}"]; | |
| N2[label="{y}"]; | |
| N3[label="{}"]; | |
| N0 -> N1[label=""]; | |
| N0 -> N2[label=""]; | |
| N1 -> N3[label=""]; | |
| N2 -> N3[label=""]; | |
| } | |
| "# | |
| ); | |
| } | |
| #[test] | |
| fn left_aligned_text() { | |
| let labels = AllNodesLabelled(vec![ | |
| "if test {\ | |
| \\l branch1\ | |
| \\l} else {\ | |
| \\l branch2\ | |
| \\l}\ | |
| \\lafterward\ | |
| \\l", | |
| "branch1", | |
| "branch2", | |
| "afterward", | |
| ]); | |
| let mut writer = Vec::new(); | |
| let g = LabelledGraphWithEscStrs::new( | |
| "syntax_tree", | |
| labels, | |
| vec![ | |
| edge(0, 1, "then", Style::None), | |
| edge(0, 2, "else", Style::None), | |
| edge(1, 3, ";", Style::None), | |
| edge(2, 3, ";", Style::None), | |
| ], | |
| ); | |
| render(&g, &mut writer).unwrap(); | |
| let mut r = String::new(); | |
| Read::read_to_string(&mut &*writer, &mut r).unwrap(); | |
| assert_eq!( | |
| r, | |
| r#"digraph syntax_tree { | |
| N0[label="if test {\l branch1\l} else {\l branch2\l}\lafterward\l"]; | |
| N1[label="branch1"]; | |
| N2[label="branch2"]; | |
| N3[label="afterward"]; | |
| N0 -> N1[label="then"]; | |
| N0 -> N2[label="else"]; | |
| N1 -> N3[label=";"]; | |
| N2 -> N3[label=";"]; | |
| } | |
| "# | |
| ); | |
| } | |
| #[test] | |
| fn simple_id_construction() { | |
| let id1 = Id::new("hello"); | |
| match id1 { | |
| Ok(_) => {} | |
| Err(..) => panic!("'hello' is not a valid value for id anymore"), | |
| } | |
| } | |
| #[test] | |
| fn badly_formatted_id() { | |
| let id2 = Id::new("Weird { struct : ure } !!!"); | |
| match id2 { | |
| Ok(_) => panic!("graphviz id suddenly allows spaces, brackets and stuff"), | |
| Err(..) => {} | |
| } | |
| } |