1use std::fmt;
6use std::io;
7
8use oxidd_core::function::Function;
9use oxidd_core::util::EdgeDropGuard;
10use oxidd_core::Edge;
11use oxidd_core::HasLevel;
12use oxidd_core::InnerNode;
13use oxidd_core::LevelNo;
14use oxidd_core::LevelView;
15use oxidd_core::Manager;
16use oxidd_core::Tag;
17
18#[derive(Clone, Copy, PartialEq, Eq, Debug)]
20pub enum EdgeStyle {
21 #[allow(missing_docs)]
22 Solid,
23 #[allow(missing_docs)]
24 Dashed,
25 #[allow(missing_docs)]
26 Dotted,
27}
28
29impl fmt::Display for EdgeStyle {
30 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31 let str = match self {
32 EdgeStyle::Solid => "solid",
33 EdgeStyle::Dashed => "dashed",
34 EdgeStyle::Dotted => "dotted",
35 };
36 write!(f, "{str}")
37 }
38}
39
40#[derive(Clone, Copy, PartialEq, Eq, Debug)]
44pub struct Color(pub u8, pub u8, pub u8);
45
46impl Color {
47 #[allow(missing_docs)]
48 pub const BLACK: Self = Color(0, 0, 0);
49}
50
51impl fmt::Display for Color {
52 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53 write!(f, "#{:02X}{:02X}{:02X}", self.0, self.1, self.2)
54 }
55}
56
57pub trait DotStyle<T: Tag> {
59 fn edge_style(no: usize, tag: T) -> (EdgeStyle, bool, Color) {
73 (
74 if tag != Default::default() {
75 EdgeStyle::Dotted
76 } else if no == 1 {
77 EdgeStyle::Dashed
78 } else {
79 EdgeStyle::Solid
80 },
81 false,
82 Color::BLACK,
83 )
84 }
85}
86
87pub fn dump_all<'a, 'id, F, VD, FD>(
96 mut file: impl io::Write,
97 manager: &F::Manager<'id>,
98 variables: impl IntoIterator<Item = (&'a F, VD)>,
99 functions: impl IntoIterator<Item = (&'a F, FD)>,
100) -> io::Result<()>
101where
102 F: 'a + Function + DotStyle<<<F::Manager<'id> as Manager>::Edge as Edge>::Tag>,
103 <F::Manager<'id> as Manager>::InnerNode: HasLevel,
104 <<F::Manager<'id> as Manager>::Edge as Edge>::Tag: fmt::Debug,
105 <F::Manager<'id> as Manager>::Terminal: fmt::Display,
106 VD: fmt::Display + Clone,
107 FD: fmt::Display,
108{
109 writeln!(file, "digraph DD {{")?;
110
111 let mut last_level = LevelNo::MAX;
112 let mut levels = vec![None; manager.num_levels() as usize];
113
114 for (f, label) in variables {
115 let node = manager
116 .get_node(f.as_edge(manager))
117 .expect_inner("variables must not be const terminals");
118 levels[node.level() as usize] = Some(label);
119 }
120
121 let mut edges = Vec::new();
122
123 for level in manager.levels() {
124 for edge in level.iter() {
125 let id = edge.node_id();
126 let node = manager
127 .get_node(edge)
128 .expect_inner("unique tables should not include terminal nodes");
129 let rc = node.ref_count();
130 let level = node.level();
131
132 for (idx, child) in node.children().enumerate() {
135 edges.push(('x', id, child.node_id(), idx, child.tag()));
136 }
137
138 if level != last_level {
139 if last_level != LevelNo::MAX {
140 writeln!(file, " }};")?;
142 }
143
144 if let Some(level_name) = &levels[level as usize] {
145 writeln!(
147 file,
148 " {{ rank = same; l{level:x} [label=\"{level_name}\", shape=none, tooltip=\"level {level}\"];"
149 )?;
150 } else {
151 writeln!(
152 file,
153 " {{ rank = same; l{level:x} [label=\"{level}\", color=\"#AAAAAA\", shape=none, tooltip=\"level {level}\"];"
154 )?;
155 }
156 last_level = level;
157 }
158
159 let color = if rc == 0 { ", color=\"#AAAAAA\"" } else { "" };
161 writeln!(
162 file,
163 " x{id:x} [label=\"\"{color}, tooltip=\"id: {id:#x}, rc: {rc}\"];"
164 )?;
165 }
166 }
167 writeln!(file, " }};")?;
168
169 const TERMINAL_LEVEL: LevelNo = LevelNo::MAX;
170 writeln!(file, " {{ rank = same; l{TERMINAL_LEVEL:x} [label=\"-\", shape=none, tooltip=\"level {TERMINAL_LEVEL} (terminals)\"];")?;
171 for edge in manager.terminals() {
172 let edge = EdgeDropGuard::new(manager, edge);
173 let id = edge.node_id();
174 let node = manager.get_node(&*edge);
175 let terminal = node.unwrap_terminal();
176 writeln!(
177 file,
178 " x{id:x} [label=\"{terminal}\", tooltip=\"terminal, id: 0x{id:x}\"];"
179 )?;
180 }
181 writeln!(file, " }};")?;
182
183 if !levels.is_empty() {
184 write!(file, " ")?;
187 for level in 0..levels.len() {
188 write!(file, "l{level:x} -> ")?;
189 }
190 writeln!(file, "l{TERMINAL_LEVEL:x} [style=invis];")?;
192 }
193
194 for (i, (func, label)) in functions.into_iter().enumerate() {
195 writeln!(file, " f{i:x} [label=\"{label}\", shape=box];")?;
197 let edge = func.as_edge(manager);
198 edges.push(('f', i, edge.node_id(), 0, edge.tag()));
199 }
200
201 for (parent_type, parent, child, idx, tag) in edges {
202 let (style, bold, color) = F::edge_style(idx, tag);
203 let bold = if bold { ",bold" } else { "" };
204 writeln!(
205 file,
206 " {parent_type}{parent:x} -> x{child:x} [style=\"{style}{bold}\", color=\"{color}\", tooltip=\"child {idx}, tag: {tag:?}\"];"
207 )?;
208 }
209
210 writeln!(file, "}}")?;
211 Ok(())
212}