1use std::borrow::Cow;
2use std::fmt::Write;
3
4use super::render::{
5 HydroEdgeProp, HydroGraphWrite, HydroNodeType, HydroWriteConfig, IndentedGraphWriter,
6};
7use crate::location::{LocationKey, LocationType};
8use crate::viz::render::VizNodeKey;
9
10pub fn escape_dot(string: &str, newline: &str) -> String {
12 string.replace('"', "\\\"").replace('\n', newline)
13}
14
15pub struct HydroDot<'a, W> {
17 base: IndentedGraphWriter<'a, W>,
18}
19
20impl<'a, W> HydroDot<'a, W> {
21 pub fn new(write: W) -> Self {
22 Self {
23 base: IndentedGraphWriter::new(write),
24 }
25 }
26
27 pub fn new_with_config(write: W, config: HydroWriteConfig<'a>) -> Self {
28 Self {
29 base: IndentedGraphWriter::new_with_config(write, config),
30 }
31 }
32}
33
34impl<W> HydroGraphWrite for HydroDot<'_, W>
35where
36 W: Write,
37{
38 type Err = super::render::GraphWriteError;
39
40 fn write_prologue(&mut self) -> Result<(), Self::Err> {
41 writeln!(
42 self.base.write,
43 "{b:i$}digraph HydroIR {{",
44 b = "",
45 i = self.base.indent
46 )?;
47 self.base.indent += 4;
48
49 writeln!(
51 self.base.write,
52 "{b:i$}layout=dot;",
53 b = "",
54 i = self.base.indent
55 )?;
56 writeln!(
57 self.base.write,
58 "{b:i$}compound=true;",
59 b = "",
60 i = self.base.indent
61 )?;
62 writeln!(
63 self.base.write,
64 "{b:i$}concentrate=true;",
65 b = "",
66 i = self.base.indent
67 )?;
68
69 const FONTS: &str = "\"Monaco,Menlo,Consolas,"Droid Sans Mono",Inconsolata,"Courier New",monospace\"";
70 writeln!(
71 self.base.write,
72 "{b:i$}node [fontname={}, style=filled];",
73 FONTS,
74 b = "",
75 i = self.base.indent
76 )?;
77 writeln!(
78 self.base.write,
79 "{b:i$}edge [fontname={}];",
80 FONTS,
81 b = "",
82 i = self.base.indent
83 )?;
84 Ok(())
85 }
86
87 fn write_node_definition(
88 &mut self,
89 node_id: VizNodeKey,
90 node_label: &super::render::NodeLabel,
91 _node_type: HydroNodeType,
92 _location_id: Option<LocationKey>,
93 _location_type: Option<LocationType>,
94 _backtrace: Option<&crate::compile::ir::backtrace::Backtrace>,
95 ) -> Result<(), Self::Err> {
96 let full_label = match node_label {
98 super::render::NodeLabel::Static(s) => s.clone(),
99 super::render::NodeLabel::WithExprs { op_name, exprs } => {
100 if exprs.is_empty() {
101 format!("{}()", op_name)
102 } else {
103 let expr_strs: Vec<String> = exprs.iter().map(|e| e.to_string()).collect();
105 format!("{}({})", op_name, expr_strs.join(", "))
106 }
107 }
108 };
109
110 let display_label = if self.base.config.use_short_labels {
112 super::render::extract_short_label(&full_label)
113 } else {
114 full_label
115 };
116
117 let escaped_label = escape_dot(&display_label, "\\l");
118 let label = format!("n{}", node_id);
119
120 write!(
121 self.base.write,
122 "{b:i$}{label} [label=\"({node_id}) {escaped_label}{}\"",
123 if escaped_label.contains("\\l") {
124 "\\l"
125 } else {
126 ""
127 },
128 b = "",
129 i = self.base.indent,
130 )?;
131 write!(self.base.write, ", shape=box, fillcolor=\"#f5f5f5\"")?;
132 writeln!(self.base.write, "]")?;
133 Ok(())
134 }
135
136 fn write_edge(
137 &mut self,
138 src_id: VizNodeKey,
139 dst_id: VizNodeKey,
140 edge_properties: &std::collections::HashSet<HydroEdgeProp>,
141 label: Option<&str>,
142 ) -> Result<(), Self::Err> {
143 let mut properties = Vec::<Cow<'static, str>>::new();
144
145 if let Some(label) = label {
146 properties.push(format!("label=\"{}\"", escape_dot(label, "\\n")).into());
147 }
148
149 let style = super::render::get_unified_edge_style(edge_properties, None, None);
150
151 properties.push(format!("color=\"{}\"", style.color).into());
152
153 if style.line_width > 1 {
154 properties.push("style=\"bold\"".into());
155 }
156
157 match style.line_pattern {
158 super::render::LinePattern::Dotted => {
159 properties.push("style=\"dotted\"".into());
160 }
161 super::render::LinePattern::Dashed => {
162 properties.push("style=\"dashed\"".into());
163 }
164 _ => {}
165 }
166
167 write!(
168 self.base.write,
169 "{b:i$}n{} -> n{}",
170 src_id,
171 dst_id,
172 b = "",
173 i = self.base.indent,
174 )?;
175
176 if !properties.is_empty() {
177 write!(self.base.write, " [")?;
178 for prop in itertools::Itertools::intersperse(properties.into_iter(), ", ".into()) {
179 write!(self.base.write, "{}", prop)?;
180 }
181 write!(self.base.write, "]")?;
182 }
183 writeln!(self.base.write)?;
184 Ok(())
185 }
186
187 fn write_location_start(
188 &mut self,
189 location_key: LocationKey,
190 location_type: LocationType,
191 ) -> Result<(), Self::Err> {
192 writeln!(
193 self.base.write,
194 "{b:i$}subgraph cluster_{location_key} {{",
195 b = "",
196 i = self.base.indent,
197 )?;
198 self.base.indent += 4;
199
200 writeln!(
202 self.base.write,
203 "{b:i$}layout=dot;",
204 b = "",
205 i = self.base.indent
206 )?;
207 writeln!(
208 self.base.write,
209 "{b:i$}label = \"{location_type:?} {location_key}\"",
210 b = "",
211 i = self.base.indent
212 )?;
213 writeln!(
214 self.base.write,
215 "{b:i$}style=filled",
216 b = "",
217 i = self.base.indent
218 )?;
219 writeln!(
220 self.base.write,
221 "{b:i$}fillcolor=\"#fafafa\"",
222 b = "",
223 i = self.base.indent
224 )?;
225 writeln!(
226 self.base.write,
227 "{b:i$}color=\"#e0e0e0\"",
228 b = "",
229 i = self.base.indent
230 )?;
231 Ok(())
232 }
233
234 fn write_node(&mut self, node_id: VizNodeKey) -> Result<(), Self::Err> {
235 writeln!(
236 self.base.write,
237 "{b:i$}n{node_id}",
238 b = "",
239 i = self.base.indent
240 )
241 }
242
243 fn write_location_end(&mut self) -> Result<(), Self::Err> {
244 self.base.indent -= 4;
245 writeln!(self.base.write, "{b:i$}}}", b = "", i = self.base.indent)
246 }
247
248 fn write_epilogue(&mut self) -> Result<(), Self::Err> {
249 self.base.indent -= 4;
250 writeln!(self.base.write, "{b:i$}}}", b = "", i = self.base.indent)
251 }
252}