shex_ast/ir/
dependency_graph.rs1use std::fmt::Display;
2
3use crate::ShapeLabelIdx;
4use petgraph::dot::{Config, Dot};
5use petgraph::graphmap::GraphMap;
6use petgraph::visit::EdgeRef;
7
8#[derive(Debug, Default)]
9pub struct DependencyGraph {
10 graph: GraphMap<ShapeLabelIdx, PosNeg, petgraph::Directed>,
11}
12
13impl DependencyGraph {
14 pub fn new() -> Self {
15 Self::default()
16 }
17
18 pub fn add_edge(&mut self, from: ShapeLabelIdx, to: ShapeLabelIdx, pos_neg: PosNeg) {
19 self.graph.add_edge(from, to, pos_neg);
20 }
21
22 pub fn neg_cycles(&self) -> Vec<Vec<(ShapeLabelIdx, ShapeLabelIdx, Vec<ShapeLabelIdx>)>> {
23 let mut result = Vec::new();
24 let scc = petgraph::algo::tarjan_scc(&self.graph);
25 for component in &scc {
26 let mut neg_cycle = Vec::new();
27 for node in component.iter().as_slice() {
28 let edges = self
29 .graph
30 .edges_directed(*node, petgraph::Direction::Outgoing);
31 for edge in edges {
32 if component.contains(&edge.target()) && edge.weight().is_neg() {
33 let mut shapes = Vec::new();
34 for node in component.iter() {
35 shapes.push(*node);
36 }
37 let target = edge.target();
38 neg_cycle.push((*node, target, shapes));
39 break;
40 }
41 }
42 }
43 if !neg_cycle.is_empty() {
44 result.push(neg_cycle);
45 }
46 }
47 result
48 }
49
50 pub fn has_neg_cycle(&self) -> bool {
51 let neg_cycles = self.neg_cycles();
52 !neg_cycles.is_empty()
53 }
54
55 pub fn all_edges(&self) -> DependencyGraphIter {
56 DependencyGraphIter {
57 inner: self.graph.all_edges(),
58 }
59 }
60}
61
62impl Display for DependencyGraph {
63 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64 write!(
65 f,
66 "{:?}",
67 Dot::with_config(&self.graph, &[Config::GraphContentOnly])
68 )
69 }
70}
71
72pub struct DependencyGraphIter<'a> {
75 inner: petgraph::graphmap::AllEdges<'a, ShapeLabelIdx, PosNeg, petgraph::Directed>,
76}
77impl Iterator for DependencyGraphIter<'_> {
78 type Item = (ShapeLabelIdx, PosNeg, ShapeLabelIdx);
79
80 fn next(&mut self) -> Option<Self::Item> {
81 self.inner
82 .next()
83 .map(|(from, to, posneg)| (from, *posneg, to))
84 }
85}
86
87#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
88pub enum PosNeg {
89 #[default]
90 Pos,
91 Neg,
92}
93
94impl PosNeg {
95 pub fn is_pos(&self) -> bool {
96 matches!(self, PosNeg::Pos)
97 }
98
99 pub fn is_neg(&self) -> bool {
100 matches!(self, PosNeg::Neg)
101 }
102
103 pub fn pos() -> PosNeg {
104 PosNeg::Pos
105 }
106
107 pub fn neg() -> PosNeg {
108 PosNeg::Neg
109 }
110
111 pub fn change(&self) -> PosNeg {
112 match self {
113 PosNeg::Pos => PosNeg::Neg,
114 PosNeg::Neg => PosNeg::Pos,
115 }
116 }
117}
118
119impl Display for PosNeg {
120 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
121 match self {
122 PosNeg::Pos => write!(f, "[+]"),
123 PosNeg::Neg => write!(f, "[-]"),
124 }
125 }
126}
127
128#[cfg(test)]
129mod tests {
130
131 #[test]
132 fn test_neg_cycle_no() {
133 use super::*;
134
135 let mut graph = DependencyGraph::new();
136 graph.add_edge(ShapeLabelIdx::from(0), ShapeLabelIdx::from(1), PosNeg::Pos);
137 graph.add_edge(ShapeLabelIdx::from(1), ShapeLabelIdx::from(0), PosNeg::Pos);
138 assert!(!graph.has_neg_cycle());
139 }
140
141 #[test]
142 fn test_neg_cycle_yes() {
143 use super::*;
144
145 let mut graph = DependencyGraph::new();
146 graph.add_edge(ShapeLabelIdx::from(0), ShapeLabelIdx::from(1), PosNeg::Pos);
147 graph.add_edge(ShapeLabelIdx::from(1), ShapeLabelIdx::from(0), PosNeg::Neg);
148 assert!(graph.has_neg_cycle());
149 }
150}