use alloc::{
string::{String, ToString},
vec::Vec,
};
use core::fmt;
use miden_core::Felt;
#[derive(Default)]
pub struct StdoutWriter;
impl fmt::Write for StdoutWriter {
fn write_str(&mut self, _s: &str) -> fmt::Result {
#[cfg(feature = "std")]
std::print!("{_s}");
Ok(())
}
}
pub fn write_stack<W: fmt::Write>(
writer: &mut W,
stack: &[Felt],
n: Option<usize>,
label: &str,
clk: impl fmt::Display,
) -> fmt::Result {
if stack.is_empty() {
writeln!(writer, "{label} empty before step {clk}.")?;
return Ok(());
}
let num_items = n.unwrap_or(stack.len());
if num_items == 0 {
writeln!(writer, "{label} state in interval [0, 0) before step {clk}:")?;
return Ok(());
}
let is_partial = num_items < stack.len();
if is_partial {
writeln!(writer, "{label} state in interval [0, {}] before step {clk}:", num_items - 1)?
} else {
writeln!(writer, "{label} state before step {clk}:")?
}
let mut stack_items = Vec::new();
for (i, element) in stack.iter().enumerate().take(num_items) {
stack_items.push((i.to_string(), Some(element.to_string())));
}
for i in stack.len()..num_items {
stack_items.push((i.to_string(), None));
}
let remaining = if num_items < stack.len() {
Some(stack.len() - num_items)
} else {
None
};
write_interval(writer, stack_items, remaining)
}
pub fn write_interval<W: fmt::Write>(
writer: &mut W,
items: Vec<(String, Option<String>)>,
remaining: Option<usize>,
) -> fmt::Result {
let max_addr_width = items.iter().map(|(addr, _)| addr.len()).max().unwrap_or(0);
let mut formatted_items: Vec<String> = items
.into_iter()
.map(|(addr, value_opt)| {
let value_string = format_value(value_opt);
format!("{addr:>max_addr_width$}: {value_string}")
})
.collect();
if let Some(count) = remaining {
formatted_items.push(format!("({count} more items)"));
}
if let Some((last, front)) = formatted_items.split_last() {
for item in front {
writeln!(writer, "├── {item}")?;
}
writeln!(writer, "└── {last}")?;
}
Ok(())
}
pub fn format_value<T: ToString>(value: Option<T>) -> String {
value.map(|v| v.to_string()).unwrap_or_else(|| "EMPTY".to_string())
}
#[cfg(test)]
mod tests {
use alloc::{string::String, vec};
use miden_core::Felt;
use super::{format_value, write_interval, write_stack};
#[test]
fn write_stack_full_uses_tree_style() {
let mut out = String::new();
let stack = [Felt::new_unchecked(3), Felt::new_unchecked(2), Felt::new_unchecked(1)];
write_stack(&mut out, &stack, None, "Stack", 0u32).unwrap();
assert_eq!(out, "Stack state before step 0:\n├── 0: 3\n├── 1: 2\n└── 2: 1\n");
}
#[test]
fn write_stack_partial_shows_remaining() {
let mut out = String::new();
let stack = [Felt::new_unchecked(9), Felt::new_unchecked(8), Felt::new_unchecked(7)];
write_stack(&mut out, &stack, Some(2), "Stack", 4u32).unwrap();
assert_eq!(
out,
"Stack state in interval [0, 1] before step 4:\n├── 0: 9\n├── 1: 8\n└── (1 more items)\n"
);
}
#[test]
fn write_stack_zero_count_does_not_underflow() {
let mut out = String::new();
let stack = [Felt::new_unchecked(9), Felt::new_unchecked(8), Felt::new_unchecked(7)];
write_stack(&mut out, &stack, Some(0), "Stack", 4u32).unwrap();
assert_eq!(out, "Stack state in interval [0, 0) before step 4:\n");
}
#[test]
fn write_interval_marks_empty_slots() {
let mut out = String::new();
let items = vec![("0".into(), Some("9".into())), ("1".into(), None)];
write_interval(&mut out, items, None).unwrap();
assert_eq!(out, "├── 0: 9\n└── 1: EMPTY\n");
}
#[test]
fn format_value_uses_empty_for_none() {
assert_eq!(format_value::<&str>(None), "EMPTY");
assert_eq!(format_value(Some(5)), "5");
}
}