use std::ffi::OsStr;
use std::fmt::{self, Debug, Display, Formatter};
use std::io::{Error, ErrorKind, Result, Write};
use std::path::Path;
use crate::{egraph::EGraph, Analysis, Language};
pub struct Dot<'a, L: Language, N: Analysis<L>> {
pub(crate) egraph: &'a EGraph<L, N>,
pub config: Vec<String>,
pub use_anchors: bool,
}
impl<'a, L, N> Dot<'a, L, N>
where
L: Language + Display,
N: Analysis<L>,
{
pub fn to_dot(&self, filename: impl AsRef<Path>) -> Result<()> {
let mut file = std::fs::File::create(filename)?;
write!(file, "{}", self)
}
pub fn with_config_line(mut self, line: impl Into<String>) -> Self {
self.config.push(line.into());
self
}
pub fn with_anchors(mut self, use_anchors: bool) -> Self {
self.use_anchors = use_anchors;
self
}
pub fn to_png(&self, filename: impl AsRef<Path>) -> Result<()> {
self.run_dot(["-Tpng".as_ref(), "-o".as_ref(), filename.as_ref()])
}
pub fn to_svg(&self, filename: impl AsRef<Path>) -> Result<()> {
self.run_dot(["-Tsvg".as_ref(), "-o".as_ref(), filename.as_ref()])
}
pub fn to_pdf(&self, filename: impl AsRef<Path>) -> Result<()> {
self.run_dot(["-Tpdf".as_ref(), "-o".as_ref(), filename.as_ref()])
}
pub fn run_dot<S, I>(&self, args: I) -> Result<()>
where
S: AsRef<OsStr>,
I: IntoIterator<Item = S>,
{
self.run("dot", args)
}
pub fn run<S1, S2, I>(&self, program: S1, args: I) -> Result<()>
where
S1: AsRef<OsStr>,
S2: AsRef<OsStr>,
I: IntoIterator<Item = S2>,
{
use std::process::{Command, Stdio};
let mut child = Command::new(program)
.args(args)
.stdin(Stdio::piped())
.stdout(Stdio::null())
.spawn()?;
let stdin = child.stdin.as_mut().expect("Failed to open stdin");
write!(stdin, "{}", self)?;
match child.wait()?.code() {
Some(0) => Ok(()),
Some(e) => Err(Error::new(
ErrorKind::Other,
format!("dot program returned error code {}", e),
)),
None => Err(Error::new(
ErrorKind::Other,
"dot program was killed by a signal",
)),
}
}
fn edge(&self, i: usize, len: usize) -> (String, String) {
assert!(i < len);
let s = |s: &str| s.to_string();
if !self.use_anchors {
return (s(""), format!("label={}", i));
}
match (len, i) {
(1, 0) => (s(""), s("")),
(2, 0) => (s(":sw"), s("")),
(2, 1) => (s(":se"), s("")),
(3, 0) => (s(":sw"), s("")),
(3, 1) => (s(":s"), s("")),
(3, 2) => (s(":se"), s("")),
(_, _) => (s(""), format!("label={}", i)),
}
}
}
impl<'a, L: Language, N: Analysis<L>> Debug for Dot<'a, L, N> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.debug_tuple("Dot").field(self.egraph).finish()
}
}
impl<'a, L, N> Display for Dot<'a, L, N>
where
L: Language + Display,
N: Analysis<L>,
{
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
writeln!(f, "digraph egraph {{")?;
writeln!(f, " compound=true")?;
writeln!(f, " clusterrank=local")?;
for line in &self.config {
writeln!(f, " {}", line)?;
}
for class in self.egraph.classes() {
writeln!(f, " subgraph cluster_{} {{", class.id)?;
writeln!(f, " style=dotted")?;
for (i, node) in class.iter().enumerate() {
writeln!(f, " {}.{}[label = \"{}\"]", class.id, i, node)?;
}
writeln!(f, " }}")?;
}
for class in self.egraph.classes() {
for (i_in_class, node) in class.iter().enumerate() {
let mut arg_i = 0;
node.try_for_each(|child| {
let (anchor, label) = self.edge(arg_i, node.len());
let child_leader = self.egraph.find(child);
if child_leader == class.id {
writeln!(
f,
" {}.{}{} -> {}.{}:n [lhead = cluster_{}, {}]",
class.id, i_in_class, anchor, class.id, i_in_class, class.id, label
)?;
} else {
writeln!(
f,
" {}.{}{} -> {}.0 [lhead = cluster_{}, {}]",
class.id, i_in_class, anchor, child, child_leader, label
)?;
}
arg_i += 1;
Ok(())
})?;
}
}
write!(f, "}}")
}
}