use std::fmt;
use std::io;
use oxidd_core::function::Function;
use oxidd_core::util::EdgeDropGuard;
use oxidd_core::Edge;
use oxidd_core::HasLevel;
use oxidd_core::InnerNode;
use oxidd_core::LevelNo;
use oxidd_core::LevelView;
use oxidd_core::Manager;
use oxidd_core::Tag;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum EdgeStyle {
#[allow(missing_docs)]
Solid,
#[allow(missing_docs)]
Dashed,
#[allow(missing_docs)]
Dotted,
}
impl fmt::Display for EdgeStyle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let str = match self {
EdgeStyle::Solid => "solid",
EdgeStyle::Dashed => "dashed",
EdgeStyle::Dotted => "dotted",
};
write!(f, "{str}")
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Color(pub u8, pub u8, pub u8);
impl Color {
#[allow(missing_docs)]
pub const BLACK: Self = Color(0, 0, 0);
}
impl fmt::Display for Color {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "#{:02X}{:02X}{:02X}", self.0, self.1, self.2)
}
}
pub trait DotStyle<T: Tag> {
fn edge_style(no: usize, tag: T) -> (EdgeStyle, bool, Color) {
(
if tag != Default::default() {
EdgeStyle::Dotted
} else if no == 1 {
EdgeStyle::Dashed
} else {
EdgeStyle::Solid
},
false,
Color::BLACK,
)
}
}
pub fn dump_all<'a, 'id, F, VD, FD>(
mut file: impl io::Write,
manager: &F::Manager<'id>,
variables: impl IntoIterator<Item = (&'a F, VD)>,
functions: impl IntoIterator<Item = (&'a F, FD)>,
) -> io::Result<()>
where
F: 'a + Function + DotStyle<<<F::Manager<'id> as Manager>::Edge as Edge>::Tag>,
<F::Manager<'id> as Manager>::InnerNode: HasLevel,
<<F::Manager<'id> as Manager>::Edge as Edge>::Tag: fmt::Debug,
<F::Manager<'id> as Manager>::Terminal: fmt::Display,
VD: fmt::Display + Clone,
FD: fmt::Display,
{
writeln!(file, "digraph DD {{")?;
let mut last_level = LevelNo::MAX;
let mut levels = vec![None; manager.num_levels() as usize];
for (f, label) in variables {
let node = manager
.get_node(f.as_edge(manager))
.expect_inner("variables must not be const terminals");
levels[node.level() as usize] = Some(label);
}
let mut edges = Vec::new();
for level in manager.levels() {
for edge in level.iter() {
let id = edge.node_id();
let node = manager
.get_node(edge)
.expect_inner("unique tables should not include terminal nodes");
let rc = node.ref_count();
let level = node.level();
for (idx, child) in node.children().enumerate() {
edges.push(('x', id, child.node_id(), idx, child.tag()));
}
if level != last_level {
if last_level != LevelNo::MAX {
writeln!(file, " }};")?;
}
if let Some(level_name) = &levels[level as usize] {
writeln!(
file,
" {{ rank = same; l{level:x} [label=\"{level_name}\", shape=none, tooltip=\"level {level}\"];"
)?;
} else {
writeln!(
file,
" {{ rank = same; l{level:x} [label=\"\", shape=none, tooltip=\"level {level}\"];"
)?;
}
last_level = level;
}
let color = if rc == 0 { ", color=\"#AAAAAA\"" } else { "" };
writeln!(
file,
" x{id:x} [label=\"\"{color}, tooltip=\"id: {id:#x}, rc: {rc}\"];"
)?;
}
}
writeln!(file, " }};")?;
const TERMINAL_LEVEL: LevelNo = LevelNo::MAX;
writeln!(file, " {{ rank = same; l{TERMINAL_LEVEL:x} [label=\"-\", shape=none, tooltip=\"level {TERMINAL_LEVEL} (terminals)\"];")?;
for edge in manager.terminals() {
let edge = EdgeDropGuard::new(manager, edge);
let id = edge.node_id();
let node = manager.get_node(&*edge);
let terminal = node.unwrap_terminal();
writeln!(
file,
" x{id:x} [label=\"{terminal}\", tooltip=\"terminal, id: 0x{id:x}\"];"
)?;
}
writeln!(file, " }};")?;
if !levels.is_empty() {
write!(file, " ")?;
for level in 0..levels.len() {
write!(file, "l{level:x} -> ")?;
}
writeln!(file, "l{TERMINAL_LEVEL:x} [style=invis];")?;
}
for (i, (func, label)) in functions.into_iter().enumerate() {
writeln!(file, " f{i:x} [label=\"{label}\", shape=box];")?;
let edge = func.as_edge(manager);
edges.push(('f', i, edge.node_id(), 0, edge.tag()));
}
for (parent_type, parent, child, idx, tag) in edges {
let (style, bold, color) = F::edge_style(idx, tag);
let bold = if bold { ",bold" } else { "" };
writeln!(
file,
" {parent_type}{parent:x} -> x{child:x} [style=\"{style}{bold}\", color=\"{color}\", tooltip=\"child {idx}, tag: {tag:?}\"];"
)?;
}
writeln!(file, "}}")?;
Ok(())
}