forked from Alepha/Alepha
149 lines
3.4 KiB
C++
149 lines
3.4 KiB
C++
static_assert( __cplusplus > 2020'99 );
|
|
|
|
#include "StackableStreambuf.h"
|
|
|
|
#include <cassert>
|
|
|
|
#include <istream>
|
|
#include <ostream>
|
|
|
|
namespace Alepha::Hydrogen::IOStreams::detail::StackableStreambuf_m
|
|
{
|
|
namespace
|
|
{
|
|
const auto index= std::ios::xalloc();
|
|
|
|
auto *&
|
|
getStackPtr( std::ios_base &ios )
|
|
{
|
|
return reinterpret_cast< std::stack< std::unique_ptr< StackableStreambuf > > *& >( ios.pword( index ) );
|
|
}
|
|
|
|
auto &
|
|
getStack( std::ios_base &ios )
|
|
{
|
|
auto &ownership= getStackPtr( ios );
|
|
if( not ownership ) ownership= new std::decay_t< decltype( *ownership ) >{};
|
|
|
|
return *ownership;
|
|
}
|
|
|
|
enum { token };
|
|
|
|
inline bool
|
|
releaseTop( std::ios_base &ios, decltype( token )= token )
|
|
{
|
|
auto &ownership= getStack( ios );
|
|
|
|
assert( not ownership.empty() );
|
|
// Since it's owned, delete happens at scope exit.
|
|
const std::unique_ptr top= std::move( ownership.top() );
|
|
ownership.pop();
|
|
|
|
top->drain();
|
|
return not ownership.empty();
|
|
}
|
|
|
|
inline bool
|
|
releaseTop( std::ostream &os )
|
|
{
|
|
const auto *const current= dynamic_cast< StackableStreambuf * >( os.rdbuf() );
|
|
if( not current ) return false;
|
|
|
|
current->unhook( os );
|
|
|
|
releaseTop( os, token );
|
|
|
|
return true;
|
|
}
|
|
|
|
inline bool
|
|
releaseTop( std::istream &is )
|
|
{
|
|
const auto *const current= dynamic_cast< StackableStreambuf * >( is.rdbuf() );
|
|
if( not current ) return false;
|
|
|
|
current->unhook( is );
|
|
|
|
releaseTop( is, token );
|
|
|
|
return true;
|
|
}
|
|
|
|
inline void
|
|
releaseStack( std::ios_base &ios )
|
|
{
|
|
delete getStackPtr( ios );
|
|
getStackPtr( ios )= nullptr;
|
|
}
|
|
|
|
void
|
|
iosCallback( const std::ios_base::event event, std::ios_base &ios, const int idx )
|
|
{
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wterminate"
|
|
|
|
if( index != idx ) throw std::logic_error{ "Wrong index." };
|
|
|
|
if( event == std::ios_base::erase_event ) releaseStack( ios );
|
|
else if( event == std::ios_base::imbue_event ) {}
|
|
else if( event == std::ios_base::copyfmt_event ) throw std::runtime_error{ "Can't copy?" };
|
|
|
|
#pragma GCC diagnostic pop
|
|
}
|
|
}
|
|
|
|
std::size_t
|
|
exports::stackDepth( std::ios &ios )
|
|
{
|
|
return getStack( ios ).size();
|
|
}
|
|
|
|
std::ostream &
|
|
impl::operator << ( std::ostream &os, PopStack )
|
|
{
|
|
if( not releaseTop( os ) ) throw std::logic_error( "OStream has no stacked streambufs!" );
|
|
return os;
|
|
}
|
|
|
|
std::istream &
|
|
impl::operator >> ( std::istream &is, PopStack )
|
|
{
|
|
if( not releaseTop( is ) ) throw std::logic_error( "IStream has no stacked streambufs!" );
|
|
return is;
|
|
}
|
|
|
|
StackableStreambuf::~StackableStreambuf() {}
|
|
|
|
StackableStreambuf::StackableStreambuf( std::ios &host )
|
|
: underlying( host.rdbuf( this ) )
|
|
{
|
|
assert( underlying );
|
|
// TODO: Atomicity for this:
|
|
if( not host.iword( index ) ) host.register_callback( iosCallback, index );
|
|
host.iword( index )= 1;
|
|
getStack( host ).emplace( this );
|
|
}
|
|
|
|
int
|
|
StackableStreambuf::overflow( const int ch )
|
|
{
|
|
if( ch == EOF ) throw std::logic_error( "EOF!" );
|
|
writeChar( ch );
|
|
|
|
return 1;
|
|
}
|
|
|
|
std::streamsize
|
|
StackableStreambuf::xsputn( const char *const data, const std::streamsize amt )
|
|
{
|
|
for( std::streamsize i= 0; i< amt; ++i ) overflow( data[ i ] );
|
|
return amt;
|
|
}
|
|
|
|
void StackableStreambuf::unhook( std::ostream &os ) const { os.rdbuf( underlying ); }
|
|
void StackableStreambuf::unhook( std::istream &is ) const { is.rdbuf( underlying ); }
|
|
|
|
std::ostream &StackableStreambuf::out( std::ostream &&res ) const { res.rdbuf( underlying ); return *&res; }
|
|
}
|