1use crate::types::StructLayout;
2use colored::Colorize;
3use comfy_table::{Cell, CellAlignment, Color, Table, presets::UTF8_FULL_CONDENSED};
4
5pub struct TableFormatter {
6 no_color: bool,
7 cache_line_size: u32,
8}
9
10impl TableFormatter {
11 pub fn new(no_color: bool, cache_line_size: u32) -> Self {
12 Self { no_color, cache_line_size }
13 }
14
15 pub fn format(&self, layouts: &[StructLayout]) -> String {
16 let mut output = String::new();
17
18 for (i, layout) in layouts.iter().enumerate() {
19 if i > 0 {
20 output.push_str("\n\n");
21 }
22 output.push_str(&self.format_struct(layout));
23 }
24
25 output
26 }
27
28 fn format_struct(&self, layout: &StructLayout) -> String {
29 let mut output = String::new();
30
31 let header = format!(
32 "struct {} ({} bytes, {:.1}% padding, {} cache line{})",
33 layout.name,
34 layout.size,
35 layout.metrics.padding_percentage,
36 layout.metrics.cache_lines_spanned,
37 if layout.metrics.cache_lines_spanned == 1 { "" } else { "s" }
38 );
39
40 if self.no_color {
41 output.push_str(&header);
42 } else {
43 output.push_str(&header.bold().to_string());
44 }
45 output.push('\n');
46
47 if let Some(ref loc) = layout.source_location {
48 output.push_str(&format!(" defined at {}:{}\n", loc.file, loc.line));
49 }
50 output.push('\n');
51
52 let mut table = Table::new();
53 table.load_preset(UTF8_FULL_CONDENSED);
54 table.set_header(vec!["Offset", "Size", "Type", "Field"]);
55
56 let mut entries: Vec<TableEntry> = Vec::new();
57
58 let mut padding_iter = layout.metrics.padding_holes.iter().peekable();
59
60 for member in &layout.members {
61 while let Some(hole) = padding_iter.peek() {
62 if member.offset.map(|o| hole.offset < o).unwrap_or(false) {
63 let hole = padding_iter.next().unwrap();
64 entries.push(TableEntry::Padding { offset: hole.offset, size: hole.size });
65 } else {
66 break;
67 }
68 }
69
70 entries.push(TableEntry::Member {
71 offset: member.offset,
72 size: member.size,
73 type_name: &member.type_name,
74 name: &member.name,
75 bit_offset: member.bit_offset,
76 bit_size: member.bit_size,
77 });
78 }
79
80 for hole in padding_iter {
81 entries.push(TableEntry::Padding { offset: hole.offset, size: hole.size });
82 }
83
84 entries.sort_by_key(|e| match e {
85 TableEntry::Member { offset, .. } => offset.unwrap_or(u64::MAX),
86 TableEntry::Padding { offset, .. } => *offset,
87 });
88
89 let mut last_cache_line: Option<u64> = None;
90
91 for entry in &entries {
92 let offset = match entry {
93 TableEntry::Member { offset: Some(o), .. } => Some(*o),
94 TableEntry::Member { offset: None, .. } => None,
95 TableEntry::Padding { offset, .. } => Some(*offset),
96 };
97
98 if let Some(off) = offset {
99 let current_cache_line = off / self.cache_line_size as u64;
100 if last_cache_line.is_some_and(|l| l != current_cache_line) {
101 let marker_offset = current_cache_line * self.cache_line_size as u64;
102 table.add_row(vec![
103 Cell::new(format!(
104 "--- cache line {} ({}) ---",
105 current_cache_line, marker_offset
106 ))
107 .set_alignment(CellAlignment::Center),
108 Cell::new(""),
109 Cell::new(""),
110 Cell::new(""),
111 ]);
112 }
113 last_cache_line = Some(current_cache_line);
114 }
115
116 match entry {
117 TableEntry::Member { offset, size, type_name, name, bit_offset, bit_size } => {
118 let offset_str = match (offset, bit_offset) {
119 (Some(o), Some(bo)) => format!("{}:{}", o, bo),
120 (Some(o), None) => o.to_string(),
121 (None, Some(bo)) => format!("?:{}", bo),
122 (None, None) => "?".to_string(),
123 };
124 let size_str = match (size, bit_size) {
125 (_, Some(bs)) => format!("{}b", bs),
126 (Some(s), None) => s.to_string(),
127 (None, None) => "?".to_string(),
128 };
129 table.add_row(vec![
130 Cell::new(offset_str),
131 Cell::new(size_str),
132 Cell::new(type_name.to_string()),
133 Cell::new(name.to_string()),
134 ]);
135 }
136 TableEntry::Padding { offset, size } => {
137 let row = if self.no_color {
138 vec![
139 Cell::new(offset.to_string()),
140 Cell::new(format!("[{} bytes]", size)),
141 Cell::new("---"),
142 Cell::new("PAD"),
143 ]
144 } else {
145 vec![
146 Cell::new(offset.to_string()).fg(Color::Yellow),
147 Cell::new(format!("[{} bytes]", size)).fg(Color::Yellow),
148 Cell::new("---").fg(Color::Yellow),
149 Cell::new("PAD").fg(Color::Yellow),
150 ]
151 };
152 table.add_row(row);
153 }
154 }
155 }
156
157 output.push_str(&table.to_string());
158
159 output.push_str(&format!(
160 "\n\nSummary: {} useful bytes, {} padding bytes ({:.1}%), cache density: {:.1}%\n",
161 layout.metrics.useful_size,
162 layout.metrics.padding_bytes,
163 layout.metrics.padding_percentage,
164 layout.metrics.cache_line_density
165 ));
166
167 output
168 }
169}
170
171enum TableEntry<'a> {
172 Member {
173 offset: Option<u64>,
174 size: Option<u64>,
175 type_name: &'a str,
176 name: &'a str,
177 bit_offset: Option<u64>,
178 bit_size: Option<u64>,
179 },
180 Padding {
181 offset: u64,
182 size: u64,
183 },
184}