static_assert( __cplusplus > 2020'99 ); #include "StackableStreambuf.h" #include #include #include 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; } }