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, expr::Language};
pub struct Dot<'a, L, M> {
egraph: &'a EGraph<L, M>,
}
impl<'a, L, M> Dot<'a, L, M> {
pub fn new(egraph: &EGraph<L, M>) -> Dot<L, M> {
Dot { egraph }
}
}
impl<'a, L: Language + Display, M> Dot<'a, L, M> {
pub fn to_dot(&self, filename: impl AsRef<Path>) -> Result<()> {
let mut file = std::fs::File::create(filename)?;
write!(file, "{}", self)?;
Ok(())
}
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",
)),
}
}
}
impl<'a, L: Language, M: Debug> Debug for Dot<'a, L, M> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "Dot({:?})", self.egraph)
}
}
fn edge(i: usize, len: usize) -> (String, String) {
assert!(i < len);
let s = |s: &str| s.to_string();
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 + Display, M> Display for Dot<'a, L, M> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
writeln!(f, "digraph egraph {{")?;
writeln!(f, " compound=true")?;
writeln!(f, " clusterrank=local")?;
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.op)?;
}
writeln!(f, " }}")?;
}
for class in self.egraph.classes() {
for (i_in_class, node) in class.iter().enumerate() {
for (arg_i, child) in node.children.iter().enumerate() {
let (anchor, label) = edge(arg_i, node.children.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
)?;
}
}
}
}
write!(f, "}}")
}
}