1use crate::vm::flowgraph::{Color, Edge, EdgeStyle, EdgeType, Node, NodeShape};
2use std::{collections::hash_map::RandomState, fmt::Write as _, hash::BuildHasher};
3
4#[derive(Debug, Clone, Copy)]
6pub enum Direction {
7 TopToBottom,
9
10 BottomToTop,
12
13 LeftToRight,
15
16 RightToLeft,
18}
19
20#[derive(Debug, Clone)]
24pub struct SubGraph {
25 label: String,
27 nodes: Vec<Node>,
29 edges: Vec<Edge>,
31 direction: Direction,
33
34 subgraphs: Vec<SubGraph>,
36}
37
38impl SubGraph {
39 #[inline]
41 fn new(label: String) -> Self {
42 Self {
43 label,
44 nodes: Vec::default(),
45 edges: Vec::default(),
46 direction: Direction::TopToBottom,
47 subgraphs: Vec::default(),
48 }
49 }
50
51 #[inline]
53 pub fn set_label(&mut self, label: String) {
54 self.label = label;
55 }
56
57 #[inline]
59 pub fn set_direction(&mut self, direction: Direction) {
60 self.direction = direction;
61 }
62
63 #[inline]
65 pub fn add_node(&mut self, location: usize, shape: NodeShape, label: Box<str>, color: Color) {
66 let node = Node::new(location, shape, label, color);
67 self.nodes.push(node);
68 }
69
70 #[inline]
72 pub fn add_edge(
73 &mut self,
74 from: usize,
75 to: usize,
76 label: Option<Box<str>>,
77 color: Color,
78 style: EdgeStyle,
79 ) -> &mut Edge {
80 let edge = Edge::new(from, to, label, color, style);
81 self.edges.push(edge);
82 self.edges.last_mut().expect("Already pushed edge")
83 }
84
85 #[inline]
87 pub fn subgraph(&mut self, label: String) -> &mut Self {
88 self.subgraphs.push(Self::new(label));
89 let result = self
90 .subgraphs
91 .last_mut()
92 .expect("We just pushed a subgraph");
93 result.set_direction(self.direction);
94 result
95 }
96
97 fn graphviz_format(&self, result: &mut String, prefix: &str) {
99 let label = format!("{}", RandomState::new().hash_one(&self.label));
100 let _ = writeln!(result, "\tsubgraph cluster_{prefix}_{label} {{");
101 result.push_str("\t\tstyle = filled;\n");
102 let _ = writeln!(
103 result,
104 "\t\tlabel = \"{}\";",
105 if self.label.is_empty() {
106 "Anonymous Function"
107 } else {
108 self.label.as_ref()
109 }
110 );
111
112 let _ = writeln!(
113 result,
114 "\t\t{prefix}_{label}_start [label=\"Start\",shape=Mdiamond,style=filled,color=green]"
115 );
116 if !self.nodes.is_empty() {
117 let _ = writeln!(result, "\t\t{prefix}_{label}_start -> {prefix}_{label}_i_0");
118 }
119
120 for node in &self.nodes {
121 let shape = match node.shape {
122 NodeShape::None => "",
123 NodeShape::Record => ", shape=record",
124 NodeShape::Diamond => ", shape=diamond",
125 };
126 let color = format!(",style=filled,color=\"{}\"", node.color);
127 let _ = writeln!(
128 result,
129 "\t\t{prefix}_{}_i_{}[label=\"{:04}: {}\"{shape}{color}];",
130 label, node.location, node.location, node.label
131 );
132 }
133
134 for edge in &self.edges {
135 let color = format!(",color=\"{}\"", edge.color);
136 let style = match (edge.style, edge.type_) {
137 (EdgeStyle::Line, EdgeType::None) => ",dir=none",
138 (EdgeStyle::Line, EdgeType::Arrow) => "",
139 (EdgeStyle::Dotted, EdgeType::None) => ",style=dotted,dir=none",
140 (EdgeStyle::Dotted, EdgeType::Arrow) => ",style=dotted",
141 (EdgeStyle::Dashed, EdgeType::None) => ",style=dashed,dir=none",
142 (EdgeStyle::Dashed, EdgeType::Arrow) => ",style=dashed,",
143 };
144 let _ = writeln!(
145 result,
146 "\t\t{prefix}_{}_i_{} -> {prefix}_{}_i_{} [label=\"{}\", len=f{style}{color}];",
147 label,
148 edge.from,
149 label,
150 edge.to,
151 edge.label.as_deref().unwrap_or("")
152 );
153 }
154 for (index, subgraph) in self.subgraphs.iter().enumerate() {
155 let prefix = format!("{prefix}_F{index}");
156 subgraph.graphviz_format(result, &prefix);
157 }
158 result.push_str("\t}\n");
159 }
160
161 fn mermaid_format(&self, result: &mut String, prefix: &str) {
163 let label = format!("{}", RandomState::new().hash_one(&self.label));
164 let rankdir = match self.direction {
165 Direction::TopToBottom => "TB",
166 Direction::BottomToTop => "BT",
167 Direction::LeftToRight => "LR",
168 Direction::RightToLeft => "RL",
169 };
170 let _ = writeln!(
171 result,
172 " subgraph {prefix}_{}[\"{}\"]",
173 label,
174 if self.label.is_empty() {
175 "Anonymous Function"
176 } else {
177 self.label.as_ref()
178 }
179 );
180 let _ = writeln!(result, " direction {rankdir}");
181 let _ = writeln!(result, " {prefix}_{label}_start{{Start}}");
182 let _ = writeln!(result, " style {prefix}_{label}_start fill:green");
183 if !self.nodes.is_empty() {
184 let _ = writeln!(result, " {prefix}_{label}_start --> {prefix}_{label}_i_0");
185 }
186
187 for node in &self.nodes {
188 let (shape_begin, shape_end) = match node.shape {
189 NodeShape::None | NodeShape::Record => ('[', ']'),
190 NodeShape::Diamond => ('{', '}'),
191 };
192 let _ = writeln!(
193 result,
194 " {prefix}_{}_i_{}{shape_begin}\"{:04}: {}\"{shape_end}",
195 label, node.location, node.location, node.label
196 );
197 if !node.color.is_none() {
198 let _ = writeln!(
199 result,
200 " style {prefix}_{}_i_{} fill:{}",
201 label, node.location, node.color
202 );
203 }
204 }
205
206 for (index, edge) in self.edges.iter().enumerate() {
207 let style = match (edge.style, edge.type_) {
208 (EdgeStyle::Line, EdgeType::None) => "---",
209 (EdgeStyle::Line, EdgeType::Arrow) => "-->",
210 (EdgeStyle::Dotted | EdgeStyle::Dashed, EdgeType::None) => "-.-",
211 (EdgeStyle::Dotted | EdgeStyle::Dashed, EdgeType::Arrow) => "-.->",
212 };
213 let _ = writeln!(
214 result,
215 " {prefix}_{}_i_{} {style}| {}| {prefix}_{}_i_{}",
216 label,
217 edge.from,
218 edge.label.as_deref().unwrap_or(""),
219 label,
220 edge.to,
221 );
222
223 if !edge.color.is_none() {
224 let _ = writeln!(
225 result,
226 " linkStyle {} stroke:{}, stroke-width: 4px",
227 index + 1,
228 edge.color
229 );
230 }
231 }
232 for (index, subgraph) in self.subgraphs.iter().enumerate() {
233 let prefix = format!("{prefix}_F{index}");
234 subgraph.mermaid_format(result, &prefix);
235 }
236 result.push_str(" end\n");
237 }
238}
239
240#[derive(Debug)]
242pub struct Graph {
243 subgraphs: Vec<SubGraph>,
244 direction: Direction,
245}
246
247impl Graph {
248 #[inline]
250 #[must_use]
251 pub fn new(direction: Direction) -> Self {
252 Self {
253 subgraphs: Vec::default(),
254 direction,
255 }
256 }
257
258 #[inline]
260 pub fn subgraph(&mut self, label: String) -> &mut SubGraph {
261 self.subgraphs.push(SubGraph::new(label));
262 let result = self
263 .subgraphs
264 .last_mut()
265 .expect("We just pushed a subgraph");
266 result.set_direction(self.direction);
267 result
268 }
269
270 #[must_use]
272 pub fn to_graphviz_format(&self) -> String {
273 let mut result = String::new();
274 result += "digraph {\n";
275 result += "\tnode [shape=record];\n";
276
277 let rankdir = match self.direction {
278 Direction::TopToBottom => "TB",
279 Direction::BottomToTop => "BT",
280 Direction::LeftToRight => "LR",
281 Direction::RightToLeft => "RL",
282 };
283 let _ = writeln!(result, "\trankdir={rankdir};");
284
285 for subgraph in &self.subgraphs {
286 subgraph.graphviz_format(&mut result, "");
287 }
288 result += "}\n";
289 result
290 }
291
292 #[must_use]
294 pub fn to_mermaid_format(&self) -> String {
295 let mut result = String::new();
296 let rankdir = match self.direction {
297 Direction::TopToBottom => "TD",
298 Direction::BottomToTop => "DT",
299 Direction::LeftToRight => "LR",
300 Direction::RightToLeft => "RL",
301 };
302 let _ = writeln!(result, "graph {rankdir}");
303
304 for subgraph in &self.subgraphs {
305 subgraph.mermaid_format(&mut result, "");
306 }
307 result += "\n";
308 result
309 }
310}