2022-07-21 20:40:44 -04:00
use std ::fmt ::Write ;
#[ derive(Debug, Clone, Copy, Default) ]
pub struct ReportOpts {
2022-07-21 20:39:47 -04:00
indentation : usize ,
2022-07-21 20:40:44 -04:00
was_nl : bool ,
2022-07-21 20:39:47 -04:00
}
2022-07-21 20:40:44 -04:00
impl ReportOpts {
2022-07-21 20:39:47 -04:00
#[ inline ]
pub fn indent ( mut self ) -> Self {
self . indentation + = 1 ;
2022-07-21 20:40:44 -04:00
self . next_line ( )
2022-07-21 20:39:47 -04:00
}
#[ inline ]
2022-07-21 20:40:44 -04:00
pub fn next_line ( mut self ) -> Self {
self . was_nl = true ;
2022-07-21 20:39:47 -04:00
self
}
#[ inline ]
2022-07-21 20:40:44 -04:00
pub fn next ( mut self ) -> Self {
self . was_nl = false ;
2022-07-21 20:39:47 -04:00
self
}
2022-07-21 20:40:44 -04:00
fn should_indent ( & self ) -> bool {
self . was_nl
2022-07-21 20:39:47 -04:00
}
}
macro_rules ! report_write {
( $f :expr , $opts :expr , $msg :literal $(, $( $tt :tt ) + ) ? ) = > {
2022-07-21 20:40:44 -04:00
< ::std ::fmt ::Arguments < '_ > as $crate ::Report > ::fmt ( & ::std ::format_args! ( $msg $(, $( $tt ) + ) ? ) , $f , $opts )
2022-07-21 20:39:47 -04:00
} ;
}
pub ( crate ) use report_write ;
2022-07-21 20:40:44 -04:00
fn write_indent ( n : usize , f : & mut impl Write ) -> std ::fmt ::Result {
for _ in 0 .. n {
f . write_str ( " " ) ? ;
2022-07-21 20:39:47 -04:00
}
2022-07-21 20:40:44 -04:00
Ok ( ( ) )
2022-07-21 20:39:47 -04:00
}
2022-07-21 20:40:44 -04:00
/// `Report` should format the output in a manner suitable for debugging, similar to [`std::fmt::Debug`] in terms of detail, but similar to [`std::fmt::Display`] in terms of readability. The options passed to [`Self::fmt`] must be respected by all implementations.
2022-07-21 20:39:47 -04:00
pub trait Report {
2022-07-21 20:40:44 -04:00
/// Formats the value using the given formatter and options.
fn fmt ( & self , f : & mut impl Write , opts : & ReportOpts ) -> std ::fmt ::Result ;
2022-07-21 20:39:47 -04:00
}
impl < T > Report for T
where
T : std ::fmt ::Display ,
{
2022-07-21 20:40:44 -04:00
fn fmt ( & self , f : & mut impl Write , opts : & ReportOpts ) -> std ::fmt ::Result {
use std ::fmt ::Error ;
struct IndentedWrite < ' a , W : Write > {
f : & ' a mut W ,
n : usize ,
}
impl < ' a , W > Write for IndentedWrite < ' a , W >
where
W : Write ,
{
fn write_str ( & mut self , s : & str ) -> Result < ( ) , Error > {
// TODO: any room for optimization?
// iterates over the lines where each str ends with the line terminator.
// after giving this a bit of thought I think it is best to indent after any
// trailing newline.
let mut ss = s . split_inclusive ( '\n' ) ;
if let Some ( mut s ) = ss . next ( ) {
self . f . write_str ( s ) ? ;
for s_next in ss {
write_indent ( self . n , & mut self . f ) ? ;
self . f . write_str ( s_next ) ? ;
s = s_next ;
}
if matches! ( s . chars ( ) . rev ( ) . next ( ) , Some ( '\n' ) ) {
write_indent ( self . n , & mut self . f ) ? ;
}
}
Ok ( ( ) )
}
fn write_char ( & mut self , c : char ) -> Result < ( ) , Error > {
self . f . write_char ( c ) ? ;
if c = = '\n' {
write_indent ( self . n , & mut self . f ) ? ;
}
Ok ( ( ) )
}
}
if opts . indentation > 0 {
if opts . should_indent ( ) {
write_indent ( opts . indentation , f ) ? ;
}
IndentedWrite {
f ,
n : opts . indentation ,
}
. write_fmt ( format_args! ( " {self} " ) )
} else {
f . write_fmt ( format_args! ( " {self} " ) )
2022-07-21 20:39:47 -04:00
}
}
}