syn_graphs/
pet.rs

1use std::{
2    any::type_name,
3    collections::{
4        hash_map::Entry::{Occupied, Vacant},
5        HashMap,
6    },
7    iter,
8};
9
10use crate::dot::{
11    self, Attrs, EdgeDirectedness, EdgeTarget, Graph, GraphDirectedness, NodeId, Stmt, StmtEdge,
12    StmtList, StmtNode, ID,
13};
14use petgraph::{
15    data::Build,
16    visit::{GetAdjacencyMatrix, GraphProp},
17};
18
19macro_rules! bail {
20    ($tokens:expr, $fmt:literal $(, $arg:expr)* $(,)?) => {
21        return Err(::syn::Error::new_spanned($tokens, ::std::format!($fmt, $($arg, )*)))
22    };
23}
24
25#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
26pub struct PortlessNode<'a> {
27    pub id: &'a ID,
28    pub attrs: Option<&'a Attrs>,
29}
30
31#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
32struct NodeInfo<'a, Ix> {
33    portless_node: PortlessNode<'a>,
34    node_id: Ix,
35}
36
37pub fn from_dot<'a, G>(dot_graph: &'a dot::Graph) -> syn::Result<G>
38where
39    G: Default
40        + Build<NodeWeight = PortlessNode<'a>, EdgeWeight = Option<&'a Attrs>>
41        + GraphProp
42        + GetAdjacencyMatrix,
43{
44    let Graph {
45        strict,
46        directedness,
47        id: _,
48        brace_token: _,
49        stmt_list: StmtList { stmts },
50    } = dot_graph;
51
52    let mut petgraph = G::default();
53
54    match (directedness, petgraph.is_directed()) {
55        (GraphDirectedness::Digraph(_), true) | (GraphDirectedness::Graph(_), false) => {}
56        (GraphDirectedness::Graph(_), true) | (GraphDirectedness::Digraph(_), false) => bail!(
57            directedness,
58            "graph direction in dot does not match compile-time direction of `{}`",
59            type_name::<G>()
60        ),
61    }
62
63    let mut id2info = HashMap::new();
64
65    for (stmt, _) in stmts {
66        if let Stmt::Node(StmtNode {
67            node_id: NodeId { id, port },
68            attrs,
69        }) = stmt
70        {
71            if port.is_some() {
72                bail!(port, "nodes with ports are not supported")
73            };
74            match id2info.entry(id) {
75                Occupied(_) => bail!(id, "duplicate node statements are not supported"),
76                Vacant(it) => {
77                    let portless_node = PortlessNode {
78                        id,
79                        attrs: attrs.as_ref(),
80                    };
81                    let node_id = petgraph.add_node(portless_node);
82                    it.insert(NodeInfo {
83                        portless_node,
84                        node_id,
85                    });
86                }
87            }
88        }
89    }
90
91    for (stmt, _) in stmts {
92        if let Stmt::Edge(StmtEdge {
93            from,
94            edges,
95            attrs: _,
96        }) = stmt
97        {
98            for it in iter::once(from).chain(edges.iter().map(|(_, it)| it)) {
99                match it {
100                    EdgeTarget::Subgraph(it) => bail!(it, "subgraphs are not supported"),
101                    EdgeTarget::NodeId(NodeId { id, port }) => {
102                        if port.is_some() {
103                            bail!(port, "nodes with ports are not supported");
104                        }
105                        match id2info.entry(id) {
106                            Occupied(_) => {}
107                            Vacant(it) => {
108                                let portless_node = PortlessNode { id, attrs: None };
109                                let node_id = petgraph.add_node(portless_node);
110                                it.insert(NodeInfo {
111                                    portless_node,
112                                    node_id,
113                                });
114                            }
115                        }
116                    }
117                }
118            }
119        }
120    }
121
122    for (stmt, _semi) in stmts {
123        match stmt {
124            Stmt::Attr(_) | Stmt::Assign(_) | Stmt::Subgraph(_) => {
125                bail!(stmt, "unsupported statement")
126            }
127            Stmt::Node(_) => {}
128            Stmt::Edge(StmtEdge { from, edges, attrs }) => {
129                let mut target_from = from;
130                for (directedness, target_to) in edges {
131                    match (directedness, petgraph.is_directed()) {
132                        (EdgeDirectedness::Directed(_), true)
133                        | (EdgeDirectedness::Undirected(_), false) => {}
134                        (EdgeDirectedness::Directed(_), false)
135                        | (EdgeDirectedness::Undirected(_), true) => {
136                            bail!(directedness, "inconsistent edge direction")
137                        }
138                    }
139
140                    let EdgeTarget::NodeId(NodeId { id, port: None }) = target_from else {
141                        unreachable!();
142                    };
143                    let node_id_from = id2info[id].node_id;
144                    let EdgeTarget::NodeId(NodeId { id, port: None }) = target_to else {
145                        unreachable!();
146                    };
147                    let node_id_to = id2info[id].node_id;
148
149                    if strict.is_some()
150                        && petgraph.is_adjacent(
151                            &petgraph.adjacency_matrix(),
152                            node_id_from,
153                            node_id_to,
154                        )
155                    {
156                        bail!(stmt, "duplicate edge on a strict graph")
157                    }
158
159                    let edge_id = petgraph.add_edge(node_id_from, node_id_to, attrs.as_ref());
160                    if edge_id.is_none() {
161                        bail!(
162                            stmt,
163                            "could not insert edge: operation not supported by {}",
164                            type_name::<G>()
165                        )
166                    }
167
168                    target_from = target_to;
169                }
170            }
171        }
172    }
173
174    Ok(petgraph)
175}
176
177#[cfg(test)]
178mod tests {
179    use std::fmt::Debug;
180
181    use petgraph::graph::{DiGraph, UnGraph};
182    use petgraph::graphmap::{DiGraphMap, UnGraphMap};
183    use petgraph::matrix_graph::{DiMatrix, UnMatrix};
184    use petgraph::stable_graph::{StableDiGraph, StableUnGraph};
185
186    use petgraph::visit::EdgeCount;
187    use syn::parse_quote;
188
189    use super::*;
190
191    trait Supports {
192        fn parallel_edges() -> bool;
193        fn self_loops() -> bool;
194    }
195
196    macro_rules! supports {
197        ($($ident:ident: $parallel:literal, $self_loops:literal);* $(;)?) => {
198            $(
199                impl<N, E> Supports for $ident<N, E> {
200                    fn parallel_edges() -> bool { $parallel }
201                    fn self_loops() -> bool { $self_loops }
202                }
203            )*
204        };
205    }
206
207    supports! {
208        DiGraph: true, true; // tested
209        UnGraph: true, true; // tested
210
211        StableDiGraph: true, true; // tested
212        StableUnGraph: true, true; // tested
213
214        DiGraphMap: false, true; // documented
215        UnGraphMap: false, true; // documented
216
217        DiMatrix: false, true; // tested
218        UnMatrix: false, true; // tested
219    }
220
221    // no point testing `List` since can't add node weights
222    // // documented
223    // impl<E> Supports for List<E> {
224    //     fn parallel_edges() -> bool { true }
225    //     fn self_loops() -> bool { true }
226    // }
227
228    #[test]
229    fn test_supports() {
230        fn test<G, T: Debug + PartialEq>()
231        where
232            G: Supports,
233            G: Default
234                + Build<NodeWeight = &'static str, EdgeWeight = (), EdgeId = T>
235                + GraphProp
236                + GetAdjacencyMatrix
237                + EdgeCount,
238        {
239            print!("testing {}...", type_name::<G>());
240
241            /////////////////
242            // Parallel edges
243            /////////////////
244            let mut graph = G::default();
245            let from = graph.add_node("from");
246            let to = graph.add_node("to");
247            assert_eq!(graph.node_count(), 2);
248
249            let edge = graph.add_edge(from, to, ());
250            assert!(edge.is_some());
251            assert_eq!(graph.edge_count(), 1);
252            match G::parallel_edges() {
253                true => {
254                    let edge2 = graph.add_edge(from, to, ());
255                    assert!(edge2.is_some());
256                    assert_ne!(edge, edge2);
257                    assert_eq!(graph.edge_count(), 2);
258                }
259                false => {
260                    let edge2 = graph.add_edge(from, to, ());
261                    assert!(edge2.is_none());
262                    assert_eq!(graph.edge_count(), 1);
263                }
264            }
265
266            /////////////
267            // Self-loops
268            /////////////
269            let mut graph = G::default();
270            let node = graph.add_node("node");
271            assert_eq!(graph.node_count(), 1);
272
273            match G::self_loops() {
274                true => {
275                    let edge = graph.add_edge(node, node, ());
276                    assert!(edge.is_some());
277                    assert_eq!(graph.edge_count(), 1);
278                }
279                false => {
280                    let edge = graph.add_edge(node, node, ());
281                    assert!(edge.is_none());
282                    assert_eq!(graph.edge_count(), 0);
283                }
284            }
285
286            ////////////////////////////////////////////////
287            // Adjacency tests should be direction-sensitive
288            ////////////////////////////////////////////////
289            let mut graph = G::default();
290            let from = graph.add_node("from");
291            let to = graph.add_node("to");
292            assert_eq!(graph.node_count(), 2);
293
294            let edge = graph.add_edge(from, to, ());
295            assert!(edge.is_some());
296            assert_eq!(graph.edge_count(), 1);
297            match graph.is_directed() {
298                true => {
299                    assert!(graph.is_adjacent(&graph.adjacency_matrix(), from, to));
300                    assert!(!graph.is_adjacent(&graph.adjacency_matrix(), to, from));
301                    let edge2 = graph.add_edge(to, from, ());
302                    assert!(edge2.is_some());
303                    assert_ne!(edge, edge2);
304                    assert_eq!(graph.edge_count(), 2);
305                    assert!(graph.is_adjacent(&graph.adjacency_matrix(), to, from));
306                }
307                false => {
308                    assert!(graph.is_adjacent(&graph.adjacency_matrix(), from, to));
309                    assert!(graph.is_adjacent(&graph.adjacency_matrix(), to, from));
310                }
311            }
312
313            println!("ok.");
314        }
315
316        test::<DiGraph<_, _>, _>();
317        test::<UnGraph<_, _>, _>();
318        test::<StableDiGraph<_, _>, _>();
319        test::<StableUnGraph<_, _>, _>();
320        test::<DiGraphMap<_, _>, _>();
321        test::<UnGraphMap<_, _>, _>();
322        test::<DiMatrix<_, _>, _>();
323        test::<UnMatrix<_, _>, _>();
324    }
325
326    #[test]
327    fn test_dot() {
328        let dot_graph: dot::Graph = parse_quote! {
329            graph {
330                a -- b -- c
331            }
332        };
333        let petgraph = from_dot::<UnGraph<_, _>>(&dot_graph).unwrap();
334        assert_eq!(petgraph.node_count(), 3);
335        assert_eq!(petgraph.edge_count(), 2);
336
337        let dot_graph: dot::Graph = parse_quote! {
338            digraph {
339                a -> b -> c;
340            }
341        };
342        let petgraph = from_dot::<DiGraph<_, _>>(&dot_graph).unwrap();
343        assert_eq!(petgraph.node_count(), 3);
344        assert_eq!(petgraph.edge_count(), 2);
345    }
346}