Skip to main content

miden_processor/host/
debug.rs

1use alloc::{
2    string::{String, ToString},
3    vec::Vec,
4};
5use core::fmt;
6
7use miden_core::Felt;
8
9// WRITER IMPLEMENTATIONS
10// ================================================================================================
11
12/// A wrapper that implements [`fmt::Write`] for `stdout` when the `std` feature is enabled.
13#[derive(Default)]
14pub struct StdoutWriter;
15
16impl fmt::Write for StdoutWriter {
17    fn write_str(&mut self, _s: &str) -> fmt::Result {
18        #[cfg(feature = "std")]
19        std::print!("{_s}");
20        Ok(())
21    }
22}
23
24// SHARED PRINTING HELPERS
25// ================================================================================================
26//
27// These functions implement the VM's tree-style debug formatting independently of any host or
28// handler, so event-based debugging procedures (e.g. `miden::core::debug`) can reuse it.
29
30/// Writes a stack-like list of elements (operand or advice stack) in the VM's debug format.
31///
32/// `stack` is ordered top-first. `n` is the number of items to show; `None` shows all of them.
33/// `label` is the human-readable name of the stack (e.g. `"Stack"` or `"Advice stack"`), and
34/// `clk` is the clock cycle reported in the header.
35pub fn write_stack<W: fmt::Write>(
36    writer: &mut W,
37    stack: &[Felt],
38    n: Option<usize>,
39    label: &str,
40    clk: impl fmt::Display,
41) -> fmt::Result {
42    if stack.is_empty() {
43        writeln!(writer, "{label} empty before step {clk}.")?;
44        return Ok(());
45    }
46
47    // Determine how many items to show
48    let num_items = n.unwrap_or(stack.len());
49
50    // Write header
51    if num_items == 0 {
52        writeln!(writer, "{label} state in interval [0, 0) before step {clk}:")?;
53        return Ok(());
54    }
55
56    let is_partial = num_items < stack.len();
57    if is_partial {
58        writeln!(writer, "{label} state in interval [0, {}] before step {clk}:", num_items - 1)?
59    } else {
60        writeln!(writer, "{label} state before step {clk}:")?
61    }
62
63    // Build stack items for display
64    let mut stack_items = Vec::new();
65    for (i, element) in stack.iter().enumerate().take(num_items) {
66        stack_items.push((i.to_string(), Some(element.to_string())));
67    }
68    // Add extra EMPTY slots if requested more than available
69    for i in stack.len()..num_items {
70        stack_items.push((i.to_string(), None));
71    }
72
73    // Calculate remaining items for partial views
74    let remaining = if num_items < stack.len() {
75        Some(stack.len() - num_items)
76    } else {
77        None
78    };
79
80    write_interval(writer, stack_items, remaining)
81}
82
83/// Writes a generic interval with proper alignment and optional remaining count.
84///
85/// Takes a vector of (address_string, optional_value_string) pairs where:
86/// - address_string: The address as a string (not pre-padded)
87/// - optional_value_string: Some(value) or None (prints "EMPTY")
88/// - remaining: Optional count of remaining items to show as "(N more items)"
89pub fn write_interval<W: fmt::Write>(
90    writer: &mut W,
91    items: Vec<(String, Option<String>)>,
92    remaining: Option<usize>,
93) -> fmt::Result {
94    // Find the maximum address width for proper alignment
95    let max_addr_width = items.iter().map(|(addr, _)| addr.len()).max().unwrap_or(0);
96
97    // Collect formatted items
98    let mut formatted_items: Vec<String> = items
99        .into_iter()
100        .map(|(addr, value_opt)| {
101            let value_string = format_value(value_opt);
102            format!("{addr:>max_addr_width$}: {value_string}")
103        })
104        .collect();
105
106    // Add remaining count if specified
107    if let Some(count) = remaining {
108        formatted_items.push(format!("({count} more items)"));
109    }
110
111    // Prints a list of items with proper tree-style indentation.
112    // All items except the last are prefixed with "├── ", and the last item with "└── ".
113    if let Some((last, front)) = formatted_items.split_last() {
114        // Print all items except the last with "├── " prefix
115        for item in front {
116            writeln!(writer, "├── {item}")?;
117        }
118        // Print the last item with "└── " prefix
119        writeln!(writer, "└── {last}")?;
120    }
121
122    Ok(())
123}
124
125// HELPER FUNCTIONS
126// ================================================================================================
127
128/// Formats a value as a string, using "EMPTY" for None values.
129pub fn format_value<T: ToString>(value: Option<T>) -> String {
130    value.map(|v| v.to_string()).unwrap_or_else(|| "EMPTY".to_string())
131}
132
133// TESTS
134// ================================================================================================
135
136#[cfg(test)]
137mod tests {
138    use alloc::{string::String, vec};
139
140    use miden_core::Felt;
141
142    use super::{format_value, write_interval, write_stack};
143
144    #[test]
145    fn write_stack_full_uses_tree_style() {
146        let mut out = String::new();
147        let stack = [Felt::new_unchecked(3), Felt::new_unchecked(2), Felt::new_unchecked(1)];
148        write_stack(&mut out, &stack, None, "Stack", 0u32).unwrap();
149        assert_eq!(out, "Stack state before step 0:\n├── 0: 3\n├── 1: 2\n└── 2: 1\n");
150    }
151
152    #[test]
153    fn write_stack_partial_shows_remaining() {
154        let mut out = String::new();
155        let stack = [Felt::new_unchecked(9), Felt::new_unchecked(8), Felt::new_unchecked(7)];
156        write_stack(&mut out, &stack, Some(2), "Stack", 4u32).unwrap();
157        assert_eq!(
158            out,
159            "Stack state in interval [0, 1] before step 4:\n├── 0: 9\n├── 1: 8\n└── (1 more items)\n"
160        );
161    }
162
163    #[test]
164    fn write_stack_zero_count_does_not_underflow() {
165        let mut out = String::new();
166        let stack = [Felt::new_unchecked(9), Felt::new_unchecked(8), Felt::new_unchecked(7)];
167        write_stack(&mut out, &stack, Some(0), "Stack", 4u32).unwrap();
168        assert_eq!(out, "Stack state in interval [0, 0) before step 4:\n");
169    }
170
171    #[test]
172    fn write_interval_marks_empty_slots() {
173        let mut out = String::new();
174        let items = vec![("0".into(), Some("9".into())), ("1".into(), None)];
175        write_interval(&mut out, items, None).unwrap();
176        assert_eq!(out, "├── 0: 9\n└── 1: EMPTY\n");
177    }
178
179    #[test]
180    fn format_value_uses_empty_for_none() {
181        assert_eq!(format_value::<&str>(None), "EMPTY");
182        assert_eq!(format_value(Some(5)), "5");
183    }
184}