use std::{
collections::hash_map::RandomState,
hash::{BuildHasher, Hash, Hasher},
};
use crate::vm::flowgraph::{Color, Edge, EdgeStyle, EdgeType, Node, NodeShape};
#[derive(Debug, Clone, Copy)]
pub enum Direction {
TopToBottom,
BottomToTop,
LeftToRight,
RightToLeft,
}
#[derive(Debug, Clone)]
pub struct SubGraph {
label: String,
nodes: Vec<Node>,
edges: Vec<Edge>,
direction: Direction,
subgraphs: Vec<SubGraph>,
}
impl SubGraph {
#[inline]
fn new(label: String) -> Self {
Self {
label,
nodes: Vec::default(),
edges: Vec::default(),
direction: Direction::TopToBottom,
subgraphs: Vec::default(),
}
}
#[inline]
pub fn set_label(&mut self, label: String) {
self.label = label;
}
#[inline]
pub fn set_direction(&mut self, direction: Direction) {
self.direction = direction;
}
#[inline]
pub fn add_node(&mut self, location: usize, shape: NodeShape, label: Box<str>, color: Color) {
let node = Node::new(location, shape, label, color);
self.nodes.push(node);
}
#[inline]
pub fn add_edge(
&mut self,
from: usize,
to: usize,
label: Option<Box<str>>,
color: Color,
style: EdgeStyle,
) -> &mut Edge {
let edge = Edge::new(from, to, label, color, style);
self.edges.push(edge);
self.edges.last_mut().expect("Already pushed edge")
}
#[inline]
pub fn subgraph(&mut self, label: String) -> &mut Self {
self.subgraphs.push(Self::new(label));
let result = self
.subgraphs
.last_mut()
.expect("We just pushed a subgraph");
result.set_direction(self.direction);
result
}
fn graphviz_format(&self, result: &mut String, prefix: &str) {
let mut hasher = RandomState::new().build_hasher();
self.label.hash(&mut hasher);
let label = format!("{}", hasher.finish());
result.push_str(&format!("\tsubgraph cluster_{prefix}_{label} {{\n"));
result.push_str("\t\tstyle = filled;\n");
result.push_str(&format!(
"\t\tlabel = \"{}\";\n",
if self.label.is_empty() {
"Anonymous Function"
} else {
self.label.as_ref()
}
));
result.push_str(&format!(
"\t\t{prefix}_{label}_start [label=\"Start\",shape=Mdiamond,style=filled,color=green]\n"
));
if !self.nodes.is_empty() {
result.push_str(&format!(
"\t\t{prefix}_{label}_start -> {prefix}_{label}_i_0\n"
));
}
for node in &self.nodes {
let shape = match node.shape {
NodeShape::None => "",
NodeShape::Record => ", shape=record",
NodeShape::Diamond => ", shape=diamond",
};
let color = format!(",style=filled,color=\"{}\"", node.color);
result.push_str(&format!(
"\t\t{prefix}_{}_i_{}[label=\"{:04}: {}\"{shape}{color}];\n",
label, node.location, node.location, node.label
));
}
for edge in &self.edges {
let color = format!(",color=\"{}\"", edge.color);
let style = match (edge.style, edge.type_) {
(EdgeStyle::Line, EdgeType::None) => ",dir=none",
(EdgeStyle::Line, EdgeType::Arrow) => "",
(EdgeStyle::Dotted, EdgeType::None) => ",style=dotted,dir=none",
(EdgeStyle::Dotted, EdgeType::Arrow) => ",style=dotted",
(EdgeStyle::Dashed, EdgeType::None) => ",style=dashed,dir=none",
(EdgeStyle::Dashed, EdgeType::Arrow) => ",style=dashed,",
};
result.push_str(&format!(
"\t\t{prefix}_{}_i_{} -> {prefix}_{}_i_{} [label=\"{}\", len=f{style}{color}];\n",
label,
edge.from,
label,
edge.to,
edge.label.as_deref().unwrap_or("")
));
}
for (index, subgraph) in self.subgraphs.iter().enumerate() {
let prefix = format!("{prefix}_F{index}");
subgraph.graphviz_format(result, &prefix);
}
result.push_str("\t}\n");
}
fn mermaid_format(&self, result: &mut String, prefix: &str) {
let mut hasher = RandomState::new().build_hasher();
self.label.hash(&mut hasher);
let label = format!("{}", hasher.finish());
let rankdir = match self.direction {
Direction::TopToBottom => "TB",
Direction::BottomToTop => "BT",
Direction::LeftToRight => "LR",
Direction::RightToLeft => "RL",
};
result.push_str(&format!(
" subgraph {prefix}_{}[\"{}\"]\n",
label,
if self.label.is_empty() {
"Anonymous Function"
} else {
self.label.as_ref()
}
));
result.push_str(&format!(" direction {rankdir}\n"));
result.push_str(&format!(" {prefix}_{label}_start{{Start}}\n"));
result.push_str(&format!(" style {prefix}_{label}_start fill:green\n"));
if !self.nodes.is_empty() {
result.push_str(&format!(
" {prefix}_{label}_start --> {prefix}_{label}_i_0\n"
));
}
for node in &self.nodes {
let (shape_begin, shape_end) = match node.shape {
NodeShape::None | NodeShape::Record => ('[', ']'),
NodeShape::Diamond => ('{', '}'),
};
result.push_str(&format!(
" {prefix}_{}_i_{}{shape_begin}\"{:04}: {}\"{shape_end}\n",
label, node.location, node.location, node.label
));
if !node.color.is_none() {
result.push_str(&format!(
" style {prefix}_{}_i_{} fill:{}\n",
label, node.location, node.color
));
}
}
for (index, edge) in self.edges.iter().enumerate() {
let style = match (edge.style, edge.type_) {
(EdgeStyle::Line, EdgeType::None) => "---",
(EdgeStyle::Line, EdgeType::Arrow) => "-->",
(EdgeStyle::Dotted | EdgeStyle::Dashed, EdgeType::None) => "-.-",
(EdgeStyle::Dotted | EdgeStyle::Dashed, EdgeType::Arrow) => "-.->",
};
result.push_str(&format!(
" {prefix}_{}_i_{} {style}| {}| {prefix}_{}_i_{}\n",
label,
edge.from,
edge.label.as_deref().unwrap_or(""),
label,
edge.to,
));
if !edge.color.is_none() {
result.push_str(&format!(
" linkStyle {} stroke:{}, stroke-width: 4px\n",
index + 1,
edge.color
));
}
}
for (index, subgraph) in self.subgraphs.iter().enumerate() {
let prefix = format!("{prefix}_F{index}");
subgraph.mermaid_format(result, &prefix);
}
result.push_str(" end\n");
}
}
#[derive(Debug)]
pub struct Graph {
subgraphs: Vec<SubGraph>,
direction: Direction,
}
impl Graph {
#[inline]
#[must_use]
pub fn new(direction: Direction) -> Self {
Self {
subgraphs: Vec::default(),
direction,
}
}
#[inline]
pub fn subgraph(&mut self, label: String) -> &mut SubGraph {
self.subgraphs.push(SubGraph::new(label));
let result = self
.subgraphs
.last_mut()
.expect("We just pushed a subgraph");
result.set_direction(self.direction);
result
}
#[must_use]
pub fn to_graphviz_format(&self) -> String {
let mut result = String::new();
result += "digraph {\n";
result += "\tnode [shape=record];\n";
let rankdir = match self.direction {
Direction::TopToBottom => "TB",
Direction::BottomToTop => "BT",
Direction::LeftToRight => "LR",
Direction::RightToLeft => "RL",
};
result += &format!("\trankdir={rankdir};\n");
for subgraph in &self.subgraphs {
subgraph.graphviz_format(&mut result, "");
}
result += "}\n";
result
}
#[must_use]
pub fn to_mermaid_format(&self) -> String {
let mut result = String::new();
let rankdir = match self.direction {
Direction::TopToBottom => "TD",
Direction::BottomToTop => "DT",
Direction::LeftToRight => "LR",
Direction::RightToLeft => "RL",
};
result += &format!("graph {rankdir}\n");
for subgraph in &self.subgraphs {
subgraph.mermaid_format(&mut result, "");
}
result += "\n";
result
}
}