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; UnGraph: true, true; StableDiGraph: true, true; StableUnGraph: true, true; DiGraphMap: false, true; UnGraphMap: false, true; DiMatrix: false, true; UnMatrix: false, true; }
220
221 #[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 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 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 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}