use core::fmt::{self, Write as _};
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
#[cfg(feature = "std")]
use std::vec::Vec;
use crate::{
arena::Arena,
id::NodeId,
traverse::{NodeEdge, Traverse},
};
#[derive(Clone, Copy)]
struct IndentedBlockState {
is_last_item: bool,
is_first_line: bool,
}
impl IndentedBlockState {
#[inline]
fn as_str(self) -> &'static str {
match (self.is_last_item, self.is_first_line) {
(false, true) => "|-- ",
(false, false) => "| ",
(true, true) => "`-- ",
(true, false) => " ",
}
}
#[inline]
fn as_str_leading(self) -> &'static str {
match (self.is_last_item, self.is_first_line) {
(false, true) => "|--",
(false, false) => "|",
(true, true) => "`--",
(true, false) => "",
}
}
#[inline]
fn as_str_trailing_spaces(self) -> &'static str {
match (self.is_last_item, self.is_first_line) {
(_, true) => " ",
(false, false) => " ",
(true, false) => " ",
}
}
#[inline]
#[must_use]
fn is_all_whitespace(self) -> bool {
self.is_last_item && !self.is_first_line
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum LineState {
BeforeIndent,
PartialIndent,
Content,
}
struct IndentWriter<'a, 'b> {
fmt: &'b mut fmt::Formatter<'a>,
line_state: LineState,
indents: Vec<IndentedBlockState>,
pending_ws_only_indent_level: usize,
}
impl<'a, 'b> IndentWriter<'a, 'b> {
#[inline]
fn new(fmt: &'b mut fmt::Formatter<'a>) -> Self {
Self {
fmt,
line_state: LineState::BeforeIndent,
indents: Vec::new(),
pending_ws_only_indent_level: 0,
}
}
fn open_item(&mut self, is_last_item: bool) -> fmt::Result {
if self.line_state != LineState::BeforeIndent {
self.fmt.write_char('\n')?;
self.line_state = LineState::BeforeIndent;
self.pending_ws_only_indent_level = 0;
}
if let Some(indent) = self.indents.last_mut() {
indent.is_first_line = false;
}
self.indents.push(IndentedBlockState {
is_last_item,
is_first_line: true,
});
Ok(())
}
#[inline]
fn close_item(&mut self) -> Result<(), ()> {
match self.indents.pop() {
Some(_) => Ok(()),
None => Err(()),
}
}
fn write_indent_partial(&mut self) -> fmt::Result {
self.pending_ws_only_indent_level = self
.indents
.iter()
.rev()
.take_while(|i| i.is_all_whitespace())
.count();
let ws_indent_first_level = self.indents.len() - self.pending_ws_only_indent_level;
let indents_to_print = &self.indents[..ws_indent_first_level];
if let Some(last) = indents_to_print.last() {
for indent in &indents_to_print[..(indents_to_print.len() - 1)] {
self.fmt.write_str(indent.as_str())?;
}
self.fmt.write_str(last.as_str_leading())?;
}
Ok(())
}
fn complete_partial_indent(&mut self) -> fmt::Result {
debug_assert_eq!(self.line_state, LineState::PartialIndent);
if let Some(last_non_ws_indent_level) =
(self.indents.len() - self.pending_ws_only_indent_level).checked_sub(1)
{
self.fmt
.write_str(self.indents[last_non_ws_indent_level].as_str_trailing_spaces())?;
}
for _ in 0..self.pending_ws_only_indent_level {
self.fmt.write_str(" ")?;
}
self.pending_ws_only_indent_level = 0;
Ok(())
}
}
impl fmt::Write for IndentWriter<'_, '_> {
fn write_str(&mut self, mut s: &str) -> fmt::Result {
while !s.is_empty() {
if self.line_state == LineState::BeforeIndent {
self.write_indent_partial()?;
self.line_state = LineState::PartialIndent;
}
let (line_end, ends_with_newline) = match s.find('\n') {
Some(pos) => (pos + 1, true),
None => (s.len(), false),
};
let content = &s[..line_end];
if !content.is_empty() {
debug_assert_ne!(
self.line_state,
LineState::BeforeIndent,
"[consistency] indent must be written since there are something to write"
);
if self.line_state == LineState::PartialIndent {
self.complete_partial_indent()?;
}
if let Some(level) = self.indents.last_mut() {
level.is_first_line = level.is_first_line && !ends_with_newline;
}
self.fmt.write_str(content)?;
self.line_state = if ends_with_newline {
LineState::BeforeIndent
} else {
LineState::Content
};
}
s = &s[line_end..];
}
Ok(())
}
}
#[derive(Clone, Copy)]
pub struct DebugPrettyPrint<'a, T> {
id: &'a NodeId,
arena: &'a Arena<T>,
}
impl<'a, T> DebugPrettyPrint<'a, T> {
#[inline]
pub(crate) fn new(id: &'a NodeId, arena: &'a Arena<T>) -> Self {
Self { id, arena }
}
}
impl<T: fmt::Display> fmt::Display for DebugPrettyPrint<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let is_alternate = f.alternate();
let mut writer = IndentWriter::new(f);
let mut traverser = self.id.traverse(self.arena);
traverser.next();
{
let data = self.arena[*self.id].get();
if is_alternate {
write!(writer, "{data:#}")?
} else {
write!(writer, "{data}")?
}
}
while let Some(id) = prepare_next_node_printing(&mut writer, &mut traverser)? {
let data = traverser.arena()[id].get();
if is_alternate {
write!(writer, "{data:#}")?
} else {
write!(writer, "{data}")?
}
}
Ok(())
}
}
impl<T: fmt::Debug> fmt::Debug for DebugPrettyPrint<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let is_alternate = f.alternate();
let mut writer = IndentWriter::new(f);
let mut traverser = self.id.traverse(self.arena);
traverser.next();
{
let data = self.arena[*self.id].get();
if is_alternate {
write!(writer, "{data:#?}")?
} else {
write!(writer, "{data:?}")?
}
}
while let Some(id) = prepare_next_node_printing(&mut writer, &mut traverser)? {
let data = traverser.arena()[id].get();
if is_alternate {
write!(writer, "{data:#?}")?
} else {
write!(writer, "{data:?}")?
}
}
Ok(())
}
}
fn prepare_next_node_printing<T>(
writer: &mut IndentWriter<'_, '_>,
traverser: &mut Traverse<'_, T>,
) -> Result<Option<NodeId>, fmt::Error> {
for ev in traverser.by_ref() {
let id = match ev {
NodeEdge::Start(id) => id,
NodeEdge::End(_) => {
if writer.close_item().is_ok() {
continue;
} else {
break;
}
}
};
let is_last_sibling = traverser.arena()[id].next_sibling().is_none();
writer.open_item(is_last_sibling)?;
return Ok(Some(id));
}
Ok(None)
}