big_code_analysis/output/
dump_ops.rs1use termcolor::{Color, StandardStream, WriteColor};
2
3use crate::ops::Ops;
4use crate::output::ColorMode;
5
6use crate::tools::{color, intense_color};
7
8pub fn dump_ops(ops: &Ops) -> std::io::Result<()> {
37 dump_ops_with_color(ops, ColorMode::Always)
38}
39
40pub fn dump_ops_with_color(ops: &Ops, color_mode: ColorMode) -> std::io::Result<()> {
52 let stdout = StandardStream::stdout(color_mode.to_color_choice());
53 let mut stdout = stdout.lock();
54 dump_space(ops, "", true, &mut stdout)?;
55 color(&mut stdout, Color::White)?;
56
57 Ok(())
58}
59
60fn dump_space(
67 space: &Ops,
68 prefix: &str,
69 last: bool,
70 stdout: &mut dyn WriteColor,
71) -> std::io::Result<()> {
72 let mut stack: Vec<(&Ops, String, bool)> = vec![(space, prefix.to_owned(), last)];
73
74 while let Some((space, prefix, last)) = stack.pop() {
75 let (pref_child, pref) = if last { (" ", "`- ") } else { ("| ", "|- ") };
76
77 color(stdout, Color::Blue)?;
78 write!(stdout, "{prefix}{pref}")?;
79
80 intense_color(stdout, Color::Yellow)?;
81 write!(stdout, "{}: ", space.kind)?;
82
83 intense_color(stdout, Color::Cyan)?;
84 write!(stdout, "{}", space.name.as_ref().map_or("", |name| name))?;
85
86 intense_color(stdout, Color::Red)?;
87 writeln!(stdout, " (@{})", space.start_line)?;
88
89 let child_prefix = format!("{prefix}{pref_child}");
90 dump_space_ops(space, &child_prefix, space.spaces.is_empty(), stdout)?;
91
92 let count = space.spaces.len();
96 for (i, child) in space.spaces.iter().enumerate().rev() {
97 stack.push((child, child_prefix.clone(), i + 1 == count));
98 }
99 }
100
101 Ok(())
102}
103
104fn dump_space_ops(
105 ops: &Ops,
106 prefix: &str,
107 last: bool,
108 stdout: &mut dyn WriteColor,
109) -> std::io::Result<()> {
110 dump_ops_values("operators", &ops.operators, prefix, false, stdout)?;
118 dump_ops_values("operands", &ops.operands, prefix, last, stdout)
119}
120
121fn dump_ops_values(
122 name: &str,
123 ops: &[String],
124 prefix: &str,
125 last: bool,
126 stdout: &mut dyn WriteColor,
127) -> std::io::Result<()> {
128 let (pref_child, pref) = if last { (" ", "`- ") } else { ("| ", "|- ") };
129
130 color(stdout, Color::Blue)?;
131 write!(stdout, "{prefix}{pref}")?;
132
133 intense_color(stdout, Color::Green)?;
134 writeln!(stdout, "{name}")?;
135
136 let Some((last_op, rest)) = ops.split_last() else {
137 return Ok(());
138 };
139
140 let prefix = format!("{prefix}{pref_child}");
141 for op in rest {
142 color(stdout, Color::Blue)?;
143 write!(stdout, "{prefix}|- ")?;
144
145 color(stdout, Color::White)?;
146 writeln!(stdout, "{op}")?;
147 }
148
149 color(stdout, Color::Blue)?;
150 write!(stdout, "{prefix}`- ")?;
151
152 color(stdout, Color::White)?;
153 writeln!(stdout, "{last_op}")
154}
155
156#[cfg(test)]
157#[allow(
158 clippy::float_cmp,
159 clippy::cast_precision_loss,
160 clippy::cast_possible_truncation,
161 clippy::cast_sign_loss,
162 clippy::similar_names,
163 clippy::doc_markdown,
164 clippy::needless_raw_string_hashes,
165 clippy::too_many_lines
166)]
167mod tests {
168 use super::*;
169 use crate::spaces::SpaceKind;
170 use termcolor::NoColor;
171
172 fn leaf_ops(operators: Vec<String>, operands: Vec<String>) -> Ops {
173 Ops {
174 name: Some("unit".to_string()),
175 name_was_lossy: false,
176 start_line: 1,
177 end_line: 1,
178 kind: SpaceKind::Unit,
179 spaces: vec![],
180 operators,
181 operands,
182 }
183 }
184
185 fn render(ops: &Ops) -> String {
186 let mut sink = NoColor::new(Vec::new());
187 dump_space(ops, "", true, &mut sink).expect("dump to in-memory sink");
188 String::from_utf8(sink.into_inner()).expect("utf-8 dump")
189 }
190
191 #[test]
192 fn dump_ops_empty_operators_and_operands_does_not_panic() {
193 assert!(dump_ops(&leaf_ops(vec![], vec![])).is_ok());
197 }
198
199 #[test]
200 fn operators_render_mid_child_connector_not_last() {
201 let ops = leaf_ops(vec!["+".to_string()], vec!["a".to_string()]);
208 let out = render(&ops);
209 assert!(
210 out.contains("|- operators"),
211 "operators must use the mid-child connector:\n{out}"
212 );
213 assert!(
214 out.contains("`- operands"),
215 "operands must use the last-child connector:\n{out}"
216 );
217 assert!(
220 out.contains("| `- +"),
221 "operator leaf must indent under the mid-child rail:\n{out}"
222 );
223 }
224
225 #[test]
226 fn deeply_nested_spaces_dump_without_stack_overflow() {
227 const DEPTH: usize = 8_000;
232 let handle = std::thread::Builder::new()
233 .stack_size(512 * 1024)
234 .spawn(|| {
235 let mut root = leaf_ops(vec!["+".to_string()], vec!["a".to_string()]);
236 let mut cursor = &mut root;
237 for _ in 0..DEPTH {
238 cursor
239 .spaces
240 .push(leaf_ops(vec!["+".to_string()], vec!["a".to_string()]));
241 cursor = cursor.spaces.last_mut().expect("just pushed");
242 }
243 let mut sink = NoColor::new(Vec::new());
244 let ok = dump_space(&root, "", true, &mut sink).is_ok();
245 let mut node = root;
251 while let Some(child) = node.spaces.pop() {
252 node = child;
253 }
254 ok
255 })
256 .expect("spawn dump thread");
257 assert!(
258 handle.join().expect("dump thread must not overflow"),
259 "deep space nesting must dump successfully"
260 );
261 }
262}