Skip to main content

mpeg_syntax_dump/render/
compact.rs

1use std::io;
2
3use crate::render::{format_hex_dump, write_indent};
4use crate::types::{BitPatternField, FieldTable, FixedWidthField, TermAnnotation, Value, VariableLengthField};
5use crate::write::SyntaxWrite;
6
7use super::text::TextRenderError;
8
9/// Compact text renderer producing LLM-optimised output.
10///
11/// Compared to [`PlainTextRenderer`](super::text::PlainTextRenderer):
12/// - Fields render as `name: value` (no bit widths, no descriptors)
13/// - Untaken conditional branches and switch cases are suppressed entirely
14/// - [`FieldTable`] values collapse to inline lists or aligned tables
15/// - No spec-grammar keywords (`if`, `for`, `switch`, braces)
16pub struct CompactTextRenderer<W> {
17    writer: W,
18    depth: usize,
19    block_stack: Vec<BlockKind>,
20    /// When set, all output is suppressed until depth returns to this level.
21    /// Used to hide untaken conditional branches.
22    suppress_depth: Option<usize>,
23}
24
25#[derive(Debug, Clone, Copy, PartialEq)]
26enum BlockKind {
27    Element,
28    /// `indented` tracks whether this if block added a depth level.
29    /// Taken branches are transparent (no indent), untaken are suppressed.
30    If { indented: bool },
31    For,
32    While,
33    DoWhile,
34    Switch,
35    /// `indented` tracks whether this case added a depth level.
36    Case { indented: bool },
37}
38
39impl<W: io::Write> CompactTextRenderer<W> {
40    pub fn new(writer: W) -> Self {
41        CompactTextRenderer {
42            writer,
43            depth: 0,
44            block_stack: Vec::new(),
45            suppress_depth: None,
46        }
47    }
48
49    pub fn into_inner(self) -> W {
50        self.writer
51    }
52
53    fn suppressed(&self) -> bool {
54        self.suppress_depth.is_some()
55    }
56
57    fn indent(&mut self) -> io::Result<()> {
58        write_indent(&mut self.writer, self.depth)
59    }
60
61    fn write_field(&mut self, name: &str, value: Option<&Value>, comment: Option<&str>) -> io::Result<()> {
62        if self.suppressed() {
63            return Ok(());
64        }
65        self.indent()?;
66        match (value, comment) {
67            (Some(val), Some(c)) => writeln!(self.writer, "{name}: {val}  // {c}"),
68            (Some(val), None) => writeln!(self.writer, "{name}: {val}"),
69            (None, Some(c)) => writeln!(self.writer, "{name}  // {c}"),
70            (None, None) => writeln!(self.writer, "{name}"),
71        }
72    }
73}
74
75impl<W: io::Write> SyntaxWrite for CompactTextRenderer<W> {
76    type Error = TextRenderError;
77
78    fn begin_element(&mut self, name: &str, params: Option<&str>) -> Result<(), Self::Error> {
79        if !self.suppressed() {
80            self.indent()?;
81            match params {
82                Some(p) => writeln!(self.writer, "{name}({p}):")?,
83                None => writeln!(self.writer, "{name}:")?,
84            }
85        }
86        self.depth += 1;
87        self.block_stack.push(BlockKind::Element);
88        Ok(())
89    }
90
91    fn end_element(&mut self) -> Result<(), Self::Error> {
92        debug_assert_eq!(self.block_stack.pop(), Some(BlockKind::Element));
93        self.depth -= 1;
94        Ok(())
95    }
96
97    fn fixed_width_field(&mut self, field: &FixedWidthField<'_>) -> Result<(), Self::Error> {
98        self.write_field(field.name, field.value.as_ref(), field.comment)?;
99        Ok(())
100    }
101
102    fn variable_length_field(&mut self, field: &VariableLengthField<'_>) -> Result<(), Self::Error> {
103        self.write_field(field.name, field.value.as_ref(), field.comment)?;
104        Ok(())
105    }
106
107    fn bit_pattern(&mut self, field: &BitPatternField<'_>) -> Result<(), Self::Error> {
108        self.write_field(field.name, Some(&field.value), None)?;
109        Ok(())
110    }
111
112    fn raw_bytes(&mut self, data: &[u8]) -> Result<(), Self::Error> {
113        if self.suppressed() {
114            return Ok(());
115        }
116        if data.len() <= 16 {
117            // Single-line compact hex
118            self.indent()?;
119            let hex: Vec<String> = data.iter().map(|b| format!("{b:02x}")).collect();
120            writeln!(self.writer, "{}", hex.join(" "))?;
121        } else {
122            let lines = format_hex_dump(data);
123            for line in &lines {
124                self.indent()?;
125                writeln!(self.writer, "{line}")?;
126            }
127        }
128        Ok(())
129    }
130
131    // ── Conditionals: suppress untaken branches ──────────────
132
133    fn begin_if(
134        &mut self,
135        _condition: &str,
136        _terms: &[TermAnnotation<'_>],
137        taken: bool,
138    ) -> Result<(), Self::Error> {
139        if !taken && !self.suppressed() {
140            self.suppress_depth = Some(self.depth);
141        }
142        // Taken branches are transparent — no extra indentation
143        self.block_stack.push(BlockKind::If { indented: false });
144        Ok(())
145    }
146
147    fn begin_else_if(
148        &mut self,
149        _condition: &str,
150        _terms: &[TermAnnotation<'_>],
151        taken: bool,
152    ) -> Result<(), Self::Error> {
153        debug_assert!(matches!(self.block_stack.last(), Some(BlockKind::If { .. })));
154        if taken {
155            if self.suppress_depth == Some(self.depth) {
156                self.suppress_depth = None;
157            }
158        } else if !self.suppressed() {
159            self.suppress_depth = Some(self.depth);
160        }
161        Ok(())
162    }
163
164    fn begin_else(&mut self, taken: bool) -> Result<(), Self::Error> {
165        debug_assert!(matches!(self.block_stack.last(), Some(BlockKind::If { .. })));
166        if taken {
167            if self.suppress_depth == Some(self.depth) {
168                self.suppress_depth = None;
169            }
170        } else if !self.suppressed() {
171            self.suppress_depth = Some(self.depth);
172        }
173        Ok(())
174    }
175
176    fn end_if(&mut self) -> Result<(), Self::Error> {
177        debug_assert!(matches!(self.block_stack.pop(), Some(BlockKind::If { .. })));
178        if self.suppress_depth == Some(self.depth) {
179            self.suppress_depth = None;
180        }
181        Ok(())
182    }
183
184    // ── Loops: compact iteration labels ──────────────────────
185
186    fn begin_for(
187        &mut self,
188        _header: &str,
189        _terms: &[TermAnnotation<'_>],
190    ) -> Result<(), Self::Error> {
191        self.depth += 1;
192        self.block_stack.push(BlockKind::For);
193        Ok(())
194    }
195
196    fn for_iteration(&mut self, _variable: &str, index: u64) -> Result<(), Self::Error> {
197        if self.suppressed() {
198            return Ok(());
199        }
200        self.indent()?;
201        writeln!(self.writer, "[{index}]:")?;
202        Ok(())
203    }
204
205    fn end_for(&mut self) -> Result<(), Self::Error> {
206        debug_assert_eq!(self.block_stack.pop(), Some(BlockKind::For));
207        self.depth -= 1;
208        Ok(())
209    }
210
211    fn begin_while(&mut self, _condition: &str) -> Result<(), Self::Error> {
212        self.depth += 1;
213        self.block_stack.push(BlockKind::While);
214        Ok(())
215    }
216
217    fn while_iteration(&mut self, index: u64) -> Result<(), Self::Error> {
218        if self.suppressed() {
219            return Ok(());
220        }
221        self.indent()?;
222        writeln!(self.writer, "[{index}]:")?;
223        Ok(())
224    }
225
226    fn end_while(&mut self) -> Result<(), Self::Error> {
227        debug_assert_eq!(self.block_stack.pop(), Some(BlockKind::While));
228        self.depth -= 1;
229        Ok(())
230    }
231
232    fn begin_do_while(&mut self) -> Result<(), Self::Error> {
233        self.depth += 1;
234        self.block_stack.push(BlockKind::DoWhile);
235        Ok(())
236    }
237
238    fn do_while_iteration(&mut self, index: u64) -> Result<(), Self::Error> {
239        if self.suppressed() {
240            return Ok(());
241        }
242        self.indent()?;
243        writeln!(self.writer, "[{index}]:")?;
244        Ok(())
245    }
246
247    fn end_do_while(&mut self, _condition: &str) -> Result<(), Self::Error> {
248        debug_assert_eq!(self.block_stack.pop(), Some(BlockKind::DoWhile));
249        self.depth -= 1;
250        Ok(())
251    }
252
253    // ── Switch/case: suppress untaken cases ──────────────────
254
255    fn begin_switch(
256        &mut self,
257        _expression: &str,
258        _terms: &[TermAnnotation<'_>],
259    ) -> Result<(), Self::Error> {
260        self.block_stack.push(BlockKind::Switch);
261        Ok(())
262    }
263
264    fn begin_case(&mut self, _label: &str, taken: bool) -> Result<(), Self::Error> {
265        if !taken && !self.suppressed() {
266            self.suppress_depth = Some(self.depth);
267        }
268        // Taken cases are transparent — no extra indentation
269        self.block_stack.push(BlockKind::Case { indented: false });
270        Ok(())
271    }
272
273    fn end_case(&mut self) -> Result<(), Self::Error> {
274        debug_assert!(matches!(self.block_stack.pop(), Some(BlockKind::Case { .. })));
275        if self.suppress_depth == Some(self.depth) {
276            self.suppress_depth = None;
277        }
278        Ok(())
279    }
280
281    fn end_switch(&mut self) -> Result<(), Self::Error> {
282        debug_assert_eq!(self.block_stack.pop(), Some(BlockKind::Switch));
283        Ok(())
284    }
285
286    // ── Field tables: collapsed output ───────────────────────
287
288    fn field_table(&mut self, table: &FieldTable<'_>) -> Result<(), Self::Error> {
289        if self.suppressed() {
290            return Ok(());
291        }
292        if table.rows.is_empty() {
293            return Ok(());
294        }
295
296        if table.columns.len() == 1 {
297            // Single column: name: [v0, v1, v2]
298            let col = &table.columns[0];
299            self.indent()?;
300            write!(self.writer, "{}: [", col.name)?;
301            for (i, row) in table.rows.iter().enumerate() {
302                if i > 0 {
303                    write!(self.writer, ", ")?;
304                }
305                if let Some(val) = row.first() {
306                    write!(self.writer, "{val}")?;
307                }
308            }
309            writeln!(self.writer, "]")?;
310        } else {
311            // Multi column: aligned table with header row
312            // Compute column widths from headers and values
313            let mut widths: Vec<usize> = table.columns.iter().map(|c| c.name.len()).collect();
314            for row in table.rows {
315                for (i, val) in row.iter().enumerate() {
316                    if i < widths.len() {
317                        let val_len = format!("{val}").len();
318                        if val_len > widths[i] {
319                            widths[i] = val_len;
320                        }
321                    }
322                }
323            }
324
325            // Header
326            self.indent()?;
327            for (i, col) in table.columns.iter().enumerate() {
328                if i > 0 {
329                    write!(self.writer, "  ")?;
330                }
331                write!(self.writer, "{:>width$}", col.name, width = widths[i])?;
332            }
333            writeln!(self.writer)?;
334
335            // Rows
336            for row in table.rows {
337                self.indent()?;
338                for (i, val) in row.iter().enumerate() {
339                    if i > 0 {
340                        write!(self.writer, "  ")?;
341                    }
342                    let formatted = format!("{val}");
343                    if i < widths.len() {
344                        write!(self.writer, "{:>width$}", formatted, width = widths[i])?;
345                    } else {
346                        write!(self.writer, "{formatted}")?;
347                    }
348                }
349                writeln!(self.writer)?;
350            }
351        }
352
353        Ok(())
354    }
355
356    // ── Assignments, comments, ellipsis ──────────────────────
357
358    fn assignment(
359        &mut self,
360        expression: &str,
361        computed_value: Option<&Value>,
362    ) -> Result<(), Self::Error> {
363        if self.suppressed() {
364            return Ok(());
365        }
366        self.indent()?;
367        match computed_value {
368            Some(val) => writeln!(self.writer, "{expression} = {val}")?,
369            None => writeln!(self.writer, "{expression}")?,
370        }
371        Ok(())
372    }
373
374    fn comment(&mut self, text: &str) -> Result<(), Self::Error> {
375        if self.suppressed() {
376            return Ok(());
377        }
378        self.indent()?;
379        writeln!(self.writer, "// {text}")?;
380        Ok(())
381    }
382
383    fn ellipsis(&mut self) -> Result<(), Self::Error> {
384        if self.suppressed() {
385            return Ok(());
386        }
387        self.indent()?;
388        writeln!(self.writer, "...")?;
389        Ok(())
390    }
391}