]> git.proxmox.com Git - rustc.git/blame - src/librustc_mir/dataflow/generic/graphviz.rs
New upstream version 1.43.0+dfsg1
[rustc.git] / src / librustc_mir / dataflow / generic / graphviz.rs
CommitLineData
dfeec247
XL
1//! A helpful diagram for debugging dataflow problems.
2
e74abb32 3use std::cell::RefCell;
dfeec247 4use std::{io, ops, str};
e74abb32 5
e74abb32 6use rustc::mir::{self, BasicBlock, Body, Location};
dfeec247 7use rustc_hir::def_id::DefId;
e74abb32 8use rustc_index::bit_set::{BitSet, HybridBitSet};
dfeec247 9use rustc_index::vec::{Idx, IndexVec};
e74abb32 10
dfeec247 11use super::{Analysis, GenKillSet, Results, ResultsRefCursor};
e74abb32 12use crate::util::graphviz_safe_def_name;
e74abb32
XL
13
14pub struct Formatter<'a, 'tcx, A>
15where
16 A: Analysis<'tcx>,
17{
18 body: &'a Body<'tcx>,
19 def_id: DefId,
20
21 // This must be behind a `RefCell` because `dot::Labeller` takes `&self`.
22 block_formatter: RefCell<BlockFormatter<'a, 'tcx, A>>,
23}
24
25impl<A> Formatter<'a, 'tcx, A>
26where
27 A: Analysis<'tcx>,
28{
29 pub fn new(
30 body: &'a Body<'tcx>,
31 def_id: DefId,
32 results: &'a Results<'tcx, A>,
dfeec247 33 state_formatter: &'a mut dyn StateFormatter<'tcx, A>,
e74abb32
XL
34 ) -> Self {
35 let block_formatter = BlockFormatter {
36 bg: Background::Light,
e74abb32 37 results: ResultsRefCursor::new(body, results),
dfeec247 38 state_formatter,
e74abb32
XL
39 };
40
dfeec247 41 Formatter { body, def_id, block_formatter: RefCell::new(block_formatter) }
e74abb32
XL
42 }
43}
44
45/// A pair of a basic block and an index into that basic blocks `successors`.
46#[derive(Copy, Clone, PartialEq, Eq, Debug)]
47pub struct CfgEdge {
48 source: BasicBlock,
49 index: usize,
50}
51
52fn outgoing_edges(body: &Body<'_>, bb: BasicBlock) -> Vec<CfgEdge> {
53 body[bb]
54 .terminator()
55 .successors()
56 .enumerate()
57 .map(|(index, _)| CfgEdge { source: bb, index })
58 .collect()
59}
60
61impl<A> dot::Labeller<'_> for Formatter<'a, 'tcx, A>
62where
63 A: Analysis<'tcx>,
64{
65 type Node = BasicBlock;
66 type Edge = CfgEdge;
67
68 fn graph_id(&self) -> dot::Id<'_> {
69 let name = graphviz_safe_def_name(self.def_id);
70 dot::Id::new(format!("graph_for_def_id_{}", name)).unwrap()
71 }
72
73 fn node_id(&self, n: &Self::Node) -> dot::Id<'_> {
74 dot::Id::new(format!("bb_{}", n.index())).unwrap()
75 }
76
77 fn node_label(&self, block: &Self::Node) -> dot::LabelText<'_> {
78 let mut label = Vec::new();
dfeec247 79 self.block_formatter.borrow_mut().write_node_label(&mut label, self.body, *block).unwrap();
e74abb32
XL
80 dot::LabelText::html(String::from_utf8(label).unwrap())
81 }
82
83 fn node_shape(&self, _n: &Self::Node) -> Option<dot::LabelText<'_>> {
84 Some(dot::LabelText::label("none"))
85 }
86
87 fn edge_label(&self, e: &Self::Edge) -> dot::LabelText<'_> {
dfeec247 88 let label = &self.body[e.source].terminator().kind.fmt_successor_labels()[e.index];
e74abb32
XL
89 dot::LabelText::label(label.clone())
90 }
91}
92
93impl<A> dot::GraphWalk<'a> for Formatter<'a, 'tcx, A>
94where
95 A: Analysis<'tcx>,
96{
97 type Node = BasicBlock;
98 type Edge = CfgEdge;
99
100 fn nodes(&self) -> dot::Nodes<'_, Self::Node> {
dfeec247 101 self.body.basic_blocks().indices().collect::<Vec<_>>().into()
e74abb32
XL
102 }
103
104 fn edges(&self) -> dot::Edges<'_, Self::Edge> {
105 self.body
106 .basic_blocks()
107 .indices()
108 .flat_map(|bb| outgoing_edges(self.body, bb))
109 .collect::<Vec<_>>()
110 .into()
111 }
112
113 fn source(&self, edge: &Self::Edge) -> Self::Node {
114 edge.source
115 }
116
117 fn target(&self, edge: &Self::Edge) -> Self::Node {
dfeec247 118 self.body[edge.source].terminator().successors().nth(edge.index).copied().unwrap()
e74abb32
XL
119 }
120}
121
122struct BlockFormatter<'a, 'tcx, A>
123where
124 A: Analysis<'tcx>,
125{
e74abb32
XL
126 results: ResultsRefCursor<'a, 'a, 'tcx, A>,
127 bg: Background,
dfeec247 128 state_formatter: &'a mut dyn StateFormatter<'tcx, A>,
e74abb32
XL
129}
130
131impl<A> BlockFormatter<'a, 'tcx, A>
132where
133 A: Analysis<'tcx>,
134{
dfeec247
XL
135 const HEADER_COLOR: &'static str = "#a0a0a0";
136
137 fn num_state_columns(&self) -> usize {
138 std::cmp::max(1, self.state_formatter.column_names().len())
139 }
140
e74abb32
XL
141 fn toggle_background(&mut self) -> Background {
142 let bg = self.bg;
143 self.bg = !bg;
144 bg
145 }
146
147 fn write_node_label(
148 &mut self,
149 w: &mut impl io::Write,
150 body: &'a Body<'tcx>,
151 block: BasicBlock,
152 ) -> io::Result<()> {
153 // Sample output:
154 // +-+-----------------------------------------------+
155 // A | bb4 |
156 // +-+----------------------------------+------------+
157 // B | MIR | STATE |
158 // +-+----------------------------------+------------+
159 // C | | (on entry) | {_0,_2,_3} |
160 // +-+----------------------------------+------------+
161 // D |0| StorageLive(_7) | |
162 // +-+----------------------------------+------------+
163 // |1| StorageLive(_8) | |
164 // +-+----------------------------------+------------+
165 // |2| _8 = &mut _1 | +_8 |
166 // +-+----------------------------------+------------+
167 // E |T| _4 = const Foo::twiddle(move _2) | -_2 |
168 // +-+----------------------------------+------------+
169 // F | | (on unwind) | {_0,_3,_8} |
170 // +-+----------------------------------+------------+
171 // | | (on successful return) | +_4 |
172 // +-+----------------------------------+------------+
173
74b04a01
XL
174 // N.B., Some attributes (`align`, `balign`) are repeated on parent elements and their
175 // children. This is because `xdot` seemed to have a hard time correctly propagating
176 // attributes. Make sure to test the output before trying to remove the redundancy.
177 // Notably, `align` was found to have no effect when applied only to <table>.
178
179 let table_fmt = concat!(
180 " border=\"1\"",
181 " cellborder=\"1\"",
182 " cellspacing=\"0\"",
183 " cellpadding=\"3\"",
184 " sides=\"rb\"",
185 );
186 write!(w, r#"<table{fmt}>"#, fmt = table_fmt)?;
e74abb32 187
dfeec247
XL
188 // A + B: Block header
189 if self.state_formatter.column_names().is_empty() {
190 self.write_block_header_simple(w, block)?;
191 } else {
192 self.write_block_header_with_state_columns(w, block)?;
193 }
e74abb32
XL
194
195 // C: Entry state
196 self.bg = Background::Light;
197 self.results.seek_to_block_start(block);
74b04a01
XL
198 let block_entry_state = self.results.get().clone();
199
200 self.write_row_with_full_state(w, "", "(on entry)")?;
e74abb32
XL
201
202 // D: Statement transfer functions
203 for (i, statement) in body[block].statements.iter().enumerate() {
204 let location = Location { block, statement_index: i };
dfeec247
XL
205 let statement_str = format!("{:?}", statement);
206 self.write_row_for_location(w, &i.to_string(), &statement_str, location)?;
e74abb32
XL
207 }
208
209 // E: Terminator transfer function
210 let terminator = body[block].terminator();
dfeec247
XL
211 let terminator_loc = body.terminator_loc(block);
212 let mut terminator_str = String::new();
213 terminator.kind.fmt_head(&mut terminator_str).unwrap();
e74abb32 214
dfeec247 215 self.write_row_for_location(w, "T", &terminator_str, terminator_loc)?;
e74abb32
XL
216
217 // F: Exit state
74b04a01
XL
218
219 // Write the full dataflow state immediately after the terminator if it differs from the
220 // state at block entry.
dfeec247 221 self.results.seek_after(terminator_loc);
74b04a01
XL
222 if self.results.get() != &block_entry_state {
223 let after_terminator_name = match terminator.kind {
224 mir::TerminatorKind::Call { destination: Some(_), .. } => "(on unwind)",
225 _ => "(on exit)",
226 };
227
228 self.write_row_with_full_state(w, "", after_terminator_name)?;
e74abb32
XL
229 }
230
74b04a01
XL
231 // Write any changes caused by terminator-specific effects
232 match terminator.kind {
233 mir::TerminatorKind::Call { destination: Some(_), .. } => {
234 let num_state_columns = self.num_state_columns();
235 self.write_row(w, "", "(on successful return)", |this, w, fmt| {
236 write!(
237 w,
238 r#"<td balign="left" colspan="{colspan}" {fmt} align="left">"#,
239 colspan = num_state_columns,
240 fmt = fmt,
241 )?;
242
243 let state_on_unwind = this.results.get().clone();
244 this.results.seek_after_assume_call_returns(terminator_loc);
245 write_diff(w, this.results.analysis(), &state_on_unwind, this.results.get())?;
246
247 write!(w, "</td>")
248 })?;
249 }
250
251 _ => {}
252 };
253
e74abb32
XL
254 write!(w, "</table>")
255 }
256
dfeec247 257 fn write_block_header_simple(
e74abb32
XL
258 &mut self,
259 w: &mut impl io::Write,
dfeec247 260 block: BasicBlock,
e74abb32 261 ) -> io::Result<()> {
dfeec247
XL
262 // +-------------------------------------------------+
263 // A | bb4 |
264 // +-----------------------------------+-------------+
265 // B | MIR | STATE |
266 // +-+---------------------------------+-------------+
267 // | | ... | |
e74abb32 268
dfeec247
XL
269 // A
270 write!(
271 w,
272 concat!("<tr>", r#"<td colspan="3" sides="tl">bb{block_id}</td>"#, "</tr>",),
273 block_id = block.index(),
274 )?;
e74abb32 275
dfeec247 276 // B
e74abb32
XL
277 write!(
278 w,
dfeec247
XL
279 concat!(
280 "<tr>",
281 r#"<td colspan="2" {fmt}>MIR</td>"#,
282 r#"<td {fmt}>STATE</td>"#,
283 "</tr>",
284 ),
285 fmt = format!("bgcolor=\"{}\" sides=\"tl\"", Self::HEADER_COLOR),
e74abb32
XL
286 )
287 }
288
dfeec247 289 fn write_block_header_with_state_columns(
e74abb32
XL
290 &mut self,
291 w: &mut impl io::Write,
dfeec247 292 block: BasicBlock,
e74abb32 293 ) -> io::Result<()> {
dfeec247
XL
294 // +------------------------------------+-------------+
295 // A | bb4 | STATE |
296 // +------------------------------------+------+------+
297 // B | MIR | GEN | KILL |
298 // +-+----------------------------------+------+------+
299 // | | ... | | |
300
301 let state_column_names = self.state_formatter.column_names();
302
303 // A
304 write!(
305 w,
306 concat!(
307 "<tr>",
308 r#"<td {fmt} colspan="2">bb{block_id}</td>"#,
309 r#"<td {fmt} colspan="{num_state_cols}">STATE</td>"#,
310 "</tr>",
311 ),
312 fmt = "sides=\"tl\"",
313 num_state_cols = state_column_names.len(),
314 block_id = block.index(),
315 )?;
e74abb32 316
dfeec247
XL
317 // B
318 let fmt = format!("bgcolor=\"{}\" sides=\"tl\"", Self::HEADER_COLOR);
319 write!(w, concat!("<tr>", r#"<td colspan="2" {fmt}>MIR</td>"#,), fmt = fmt,)?;
e74abb32 320
dfeec247
XL
321 for name in state_column_names {
322 write!(w, "<td {fmt}>{name}</td>", fmt = fmt, name = name)?;
323 }
e74abb32 324
dfeec247
XL
325 write!(w, "</tr>")
326 }
327
328 /// Write a row with the given index and MIR, using the function argument to fill in the
329 /// "STATE" column(s).
330 fn write_row<W: io::Write>(
331 &mut self,
332 w: &mut W,
333 i: &str,
334 mir: &str,
335 f: impl FnOnce(&mut Self, &mut W, &str) -> io::Result<()>,
336 ) -> io::Result<()> {
337 let bg = self.toggle_background();
74b04a01
XL
338 let valign = if mir.starts_with("(on ") && mir != "(on entry)" { "bottom" } else { "top" };
339
340 let fmt = format!("valign=\"{}\" sides=\"tl\" {}", valign, bg.attr());
e74abb32
XL
341
342 write!(
343 w,
dfeec247
XL
344 concat!(
345 "<tr>",
346 r#"<td {fmt} align="right">{i}</td>"#,
347 r#"<td {fmt} align="left">{mir}</td>"#,
348 ),
e74abb32 349 i = i,
dfeec247 350 fmt = fmt,
e74abb32
XL
351 mir = dot::escape_html(mir),
352 )?;
353
dfeec247
XL
354 f(self, w, &fmt)?;
355 write!(w, "</tr>")
356 }
357
358 fn write_row_with_full_state(
359 &mut self,
360 w: &mut impl io::Write,
361 i: &str,
362 mir: &str,
363 ) -> io::Result<()> {
364 self.write_row(w, i, mir, |this, w, fmt| {
365 let state = this.results.get();
366 let analysis = this.results.analysis();
367
e74abb32
XL
368 write!(
369 w,
dfeec247
XL
370 r#"<td colspan="{colspan}" {fmt} align="left">{{"#,
371 colspan = this.num_state_columns(),
372 fmt = fmt,
e74abb32 373 )?;
74b04a01 374 pretty_print_state_elems(w, analysis, state.iter(), ", ", LIMIT_30_ALIGN_1)?;
dfeec247
XL
375 write!(w, "}}</td>")
376 })
377 }
378
379 fn write_row_for_location(
380 &mut self,
381 w: &mut impl io::Write,
382 i: &str,
383 mir: &str,
384 location: Location,
385 ) -> io::Result<()> {
386 self.write_row(w, i, mir, |this, w, fmt| {
387 this.state_formatter.write_state_for_location(w, fmt, &mut this.results, location)
388 })
389 }
390}
391
392/// Controls what gets printed under the `STATE` header.
393pub trait StateFormatter<'tcx, A>
394where
395 A: Analysis<'tcx>,
396{
397 /// The columns that will get printed under `STATE`.
398 fn column_names(&self) -> &[&str];
399
400 fn write_state_for_location(
401 &mut self,
402 w: &mut dyn io::Write,
403 fmt: &str,
404 results: &mut ResultsRefCursor<'_, '_, 'tcx, A>,
405 location: Location,
406 ) -> io::Result<()>;
407}
408
409/// Prints a single column containing the state vector immediately *after* each statement.
410pub struct SimpleDiff<T: Idx> {
411 prev_state: BitSet<T>,
412 prev_loc: Location,
413}
414
415impl<T: Idx> SimpleDiff<T> {
dfeec247
XL
416 pub fn new(bits_per_block: usize) -> Self {
417 SimpleDiff { prev_state: BitSet::new_empty(bits_per_block), prev_loc: Location::START }
418 }
419}
420
421impl<A> StateFormatter<'tcx, A> for SimpleDiff<A::Idx>
422where
423 A: Analysis<'tcx>,
424{
425 fn column_names(&self) -> &[&str] {
426 &[]
427 }
428
429 fn write_state_for_location(
430 &mut self,
431 mut w: &mut dyn io::Write,
432 fmt: &str,
433 results: &mut ResultsRefCursor<'_, '_, 'tcx, A>,
434 location: Location,
435 ) -> io::Result<()> {
436 if location.statement_index == 0 {
437 results.seek_to_block_start(location.block);
438 self.prev_state.overwrite(results.get());
439 } else {
440 // Ensure that we are visiting statements in order, so `prev_state` is correct.
441 assert_eq!(self.prev_loc.successor_within_block(), location);
e74abb32
XL
442 }
443
dfeec247 444 self.prev_loc = location;
74b04a01
XL
445 write!(w, r#"<td {fmt} balign="left" align="left">"#, fmt = fmt)?;
446 results.seek_after(location);
dfeec247
XL
447 let curr_state = results.get();
448 write_diff(&mut w, results.analysis(), &self.prev_state, curr_state)?;
449 self.prev_state.overwrite(curr_state);
450 write!(w, "</td>")
451 }
452}
453
454/// Prints two state columns, one containing only the "before" effect of each statement and one
455/// containing the full effect.
456pub struct TwoPhaseDiff<T: Idx> {
457 prev_state: BitSet<T>,
458 prev_loc: Location,
459}
460
461impl<T: Idx> TwoPhaseDiff<T> {
dfeec247
XL
462 pub fn new(bits_per_block: usize) -> Self {
463 TwoPhaseDiff { prev_state: BitSet::new_empty(bits_per_block), prev_loc: Location::START }
464 }
465}
466
467impl<A> StateFormatter<'tcx, A> for TwoPhaseDiff<A::Idx>
468where
469 A: Analysis<'tcx>,
470{
471 fn column_names(&self) -> &[&str] {
74b04a01 472 &["BEFORE", " AFTER"]
dfeec247
XL
473 }
474
475 fn write_state_for_location(
476 &mut self,
477 mut w: &mut dyn io::Write,
478 fmt: &str,
479 results: &mut ResultsRefCursor<'_, '_, 'tcx, A>,
480 location: Location,
481 ) -> io::Result<()> {
482 if location.statement_index == 0 {
483 results.seek_to_block_start(location.block);
484 self.prev_state.overwrite(results.get());
485 } else {
486 // Ensure that we are visiting statements in order, so `prev_state` is correct.
487 assert_eq!(self.prev_loc.successor_within_block(), location);
e74abb32
XL
488 }
489
dfeec247
XL
490 self.prev_loc = location;
491
74b04a01 492 // Before
dfeec247
XL
493
494 write!(w, r#"<td {fmt} align="left">"#, fmt = fmt)?;
495 results.seek_before(location);
496 let curr_state = results.get();
497 write_diff(&mut w, results.analysis(), &self.prev_state, curr_state)?;
498 self.prev_state.overwrite(curr_state);
499 write!(w, "</td>")?;
500
74b04a01 501 // After
dfeec247
XL
502
503 write!(w, r#"<td {fmt} align="left">"#, fmt = fmt)?;
504 results.seek_after(location);
505 let curr_state = results.get();
506 write_diff(&mut w, results.analysis(), &self.prev_state, curr_state)?;
507 self.prev_state.overwrite(curr_state);
508 write!(w, "</td>")
509 }
510}
511
512/// Prints the gen/kill set for the entire block.
513pub struct BlockTransferFunc<'a, 'tcx, T: Idx> {
514 body: &'a mir::Body<'tcx>,
515 trans_for_block: IndexVec<BasicBlock, GenKillSet<T>>,
516}
517
518impl<T: Idx> BlockTransferFunc<'mir, 'tcx, T> {
dfeec247
XL
519 pub fn new(
520 body: &'mir mir::Body<'tcx>,
521 trans_for_block: IndexVec<BasicBlock, GenKillSet<T>>,
522 ) -> Self {
523 BlockTransferFunc { body, trans_for_block }
524 }
525}
526
527impl<A> StateFormatter<'tcx, A> for BlockTransferFunc<'mir, 'tcx, A::Idx>
528where
529 A: Analysis<'tcx>,
530{
531 fn column_names(&self) -> &[&str] {
532 &["GEN", "KILL"]
533 }
534
535 fn write_state_for_location(
536 &mut self,
537 mut w: &mut dyn io::Write,
538 fmt: &str,
539 results: &mut ResultsRefCursor<'_, '_, 'tcx, A>,
540 location: Location,
541 ) -> io::Result<()> {
542 // Only print a single row.
543 if location.statement_index != 0 {
544 return Ok(());
545 }
546
547 let block_trans = &self.trans_for_block[location.block];
548 let rowspan = self.body.basic_blocks()[location.block].statements.len();
549
550 for set in &[&block_trans.gen, &block_trans.kill] {
e74abb32
XL
551 write!(
552 w,
74b04a01 553 r#"<td {fmt} rowspan="{rowspan}" balign="left" align="left">"#,
dfeec247
XL
554 fmt = fmt,
555 rowspan = rowspan
e74abb32 556 )?;
dfeec247 557
74b04a01 558 pretty_print_state_elems(&mut w, results.analysis(), set.iter(), BR_LEFT, None)?;
dfeec247 559 write!(w, "</td>")?;
e74abb32
XL
560 }
561
dfeec247 562 Ok(())
e74abb32
XL
563 }
564}
565
dfeec247
XL
566/// Writes two lines, one containing the added bits and one the removed bits.
567fn write_diff<A: Analysis<'tcx>>(
568 w: &mut impl io::Write,
569 analysis: &A,
570 from: &BitSet<A::Idx>,
571 to: &BitSet<A::Idx>,
572) -> io::Result<()> {
573 assert_eq!(from.domain_size(), to.domain_size());
574 let len = from.domain_size();
575
576 let mut set = HybridBitSet::new_empty(len);
577 let mut clear = HybridBitSet::new_empty(len);
578
579 // FIXME: Implement a lazy iterator over the symmetric difference of two bitsets.
580 for i in (0..len).map(|i| A::Idx::new(i)) {
581 match (from.contains(i), to.contains(i)) {
582 (false, true) => set.insert(i),
583 (true, false) => clear.insert(i),
584 _ => continue,
585 };
586 }
e74abb32 587
dfeec247
XL
588 if !set.is_empty() {
589 write!(w, r#"<font color="darkgreen">+"#)?;
74b04a01 590 pretty_print_state_elems(w, analysis, set.iter(), ", ", LIMIT_30_ALIGN_1)?;
dfeec247
XL
591 write!(w, r#"</font>"#)?;
592 }
e74abb32 593
dfeec247 594 if !set.is_empty() && !clear.is_empty() {
74b04a01 595 write!(w, "{}", BR_LEFT)?;
dfeec247
XL
596 }
597
598 if !clear.is_empty() {
599 write!(w, r#"<font color="red">-"#)?;
74b04a01 600 pretty_print_state_elems(w, analysis, clear.iter(), ", ", LIMIT_30_ALIGN_1)?;
dfeec247 601 write!(w, r#"</font>"#)?;
e74abb32 602 }
dfeec247
XL
603
604 Ok(())
e74abb32
XL
605}
606
74b04a01
XL
607const BR_LEFT: &str = r#"<br align="left"/>"#;
608const BR_LEFT_SPACE: &str = r#"<br align="left"/> "#;
609
dfeec247 610/// Line break policy that breaks at 40 characters and starts the next line with a single space.
74b04a01 611const LIMIT_30_ALIGN_1: Option<LineBreak> = Some(LineBreak { sequence: BR_LEFT_SPACE, limit: 30 });
dfeec247
XL
612
613struct LineBreak {
614 sequence: &'static str,
615 limit: usize,
616}
617
618/// Formats each `elem` using the pretty printer provided by `analysis` into a list with the given
619/// separator (`sep`).
620///
621/// Optionally, it will break lines using the given character sequence (usually `<br/>`) and
622/// character limit.
e74abb32
XL
623fn pretty_print_state_elems<A>(
624 w: &mut impl io::Write,
625 analysis: &A,
626 elems: impl Iterator<Item = A::Idx>,
dfeec247
XL
627 sep: &str,
628 line_break: Option<LineBreak>,
629) -> io::Result<bool>
e74abb32
XL
630where
631 A: Analysis<'tcx>,
632{
dfeec247
XL
633 let sep_width = sep.chars().count();
634
635 let mut buf = Vec::new();
636
e74abb32 637 let mut first = true;
dfeec247
XL
638 let mut curr_line_width = 0;
639 let mut line_break_inserted = false;
640
e74abb32 641 for idx in elems {
dfeec247
XL
642 buf.clear();
643 analysis.pretty_print_idx(&mut buf, idx)?;
644 let idx_str =
645 str::from_utf8(&buf).expect("Output of `pretty_print_idx` must be valid UTF-8");
646 let escaped = dot::escape_html(idx_str);
647 let escaped_width = escaped.chars().count();
648
74b04a01
XL
649 if first {
650 first = false;
651 } else {
652 write!(w, "{}", sep)?;
653 curr_line_width += sep_width;
654
655 if let Some(line_break) = &line_break {
656 if curr_line_width + sep_width + escaped_width > line_break.limit {
657 write!(w, "{}", line_break.sequence)?;
658 line_break_inserted = true;
659 curr_line_width = 0;
660 }
dfeec247 661 }
e74abb32
XL
662 }
663
dfeec247
XL
664 write!(w, "{}", escaped)?;
665 curr_line_width += escaped_width;
e74abb32
XL
666 }
667
dfeec247 668 Ok(line_break_inserted)
e74abb32
XL
669}
670
671/// The background color used for zebra-striping the table.
672#[derive(Clone, Copy)]
673enum Background {
674 Light,
675 Dark,
676}
677
678impl Background {
679 fn attr(self) -> &'static str {
680 match self {
681 Self::Dark => "bgcolor=\"#f0f0f0\"",
682 Self::Light => "",
683 }
684 }
685}
686
687impl ops::Not for Background {
688 type Output = Self;
689
690 fn not(self) -> Self {
691 match self {
692 Self::Light => Self::Dark,
693 Self::Dark => Self::Light,
694 }
695 }
696}