diff --git a/Utility/StackableStreambuf.h b/Utility/StackableStreambuf.h index 0954e9b..7292a87 100644 --- a/Utility/StackableStreambuf.h +++ b/Utility/StackableStreambuf.h @@ -7,6 +7,8 @@ static_assert( __cplusplus > 2020'00 ); #include #include #include +#include +#include namespace Alepha::Hydrogen::Utility ::detail:: stackable_streambuf { @@ -23,19 +25,25 @@ namespace Alepha::Hydrogen::Utility ::detail:: stackable_streambuf inline namespace impl { - void releaseStack( std::ios_base &ios ); - bool releaseTop( std::ostream &os ); - void iosCallback( const std::ios_base::event event, std::ios_base &ios, const int idx ); } + inline auto & + getStack( std::ios_base &ios ) + { + auto &ownership= reinterpret_cast< std::stack< std::unique_ptr< StackableStreambuf > > *& >( ios.pword( index ) ); + if( not ownership ) ownership= new std::decay_t< decltype( *ownership ) >{}; + + return *ownership; + } + struct exports::StackableStreambuf : virtual public std::streambuf { public: std::streambuf *underlying; - ~StackableStreambuf() {} + ~StackableStreambuf() override {} // Children must be created by `new`. explicit @@ -45,9 +53,10 @@ namespace Alepha::Hydrogen::Utility ::detail:: stackable_streambuf // TODO: Atomicity for this: if( not host.iword( index ) ) host.register_callback( iosCallback, index ); host.iword( index )= 1; + getStack( host ).emplace( this ); } - std::ostream out() const { return std::ostream{ underlying }; } + auto out() const { return std::ostream{ underlying }; } virtual void writeChar( const char ch )= 0; virtual void drain()= 0; @@ -69,22 +78,36 @@ namespace Alepha::Hydrogen::Utility ::detail:: stackable_streambuf } }; - inline bool - impl::releaseTop( std::ostream &os ) - { - auto *const streambuf= os.rdbuf(); - if( not streambuf ) return false; - auto *const stacked= dynamic_cast< StackableStreambuf * >( streambuf ); - if( not stacked ) return false; + enum { token }; + + inline bool + releaseTop( std::ios_base &ios, decltype( token )= token ) + { + auto &ownership= getStack( ios ); + + // 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; + + os.rdbuf( current->underlying ); + + releaseTop( os, token ); - stacked->drain(); - os.rdbuf( stacked->underlying ); - delete stacked; return true; } inline void - impl::releaseStack( std::ios_base &ios ) + releaseStack( std::ios_base &ios ) { auto &os= dynamic_cast< std::ostream & >( ios ); @@ -94,9 +117,16 @@ namespace Alepha::Hydrogen::Utility ::detail:: stackable_streambuf inline void impl::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 } template< typename T > diff --git a/string_algorithms.cpp b/string_algorithms.cpp index f14993f..8d33b83 100644 --- a/string_algorithms.cpp +++ b/string_algorithms.cpp @@ -7,6 +7,7 @@ static_assert( __cplusplus > 2020'00 ); #include #include "error.h" +#include "AutoRAII.h" namespace Alepha::Cavorite ::detail:: string_algorithms { @@ -27,7 +28,8 @@ namespace Alepha::Cavorite ::detail:: string_algorithms VarMap substitutions; std::stringbuf varName; char sigil; - enum { Symbol, Normal } mode= Normal; + enum { Symbol= 1, Normal= 0 } mode= Normal; + int throws= 0; void writeChar( const char ch ) @@ -80,32 +82,51 @@ namespace Alepha::Cavorite ::detail:: string_algorithms void drain() { - if( mode != Normal ) throw std::runtime_error{ "Unterminated variable `" + varName.str() + " in expansion." }; + std::cerr << "Drain called, and mode is: " << mode << std::endl; + if( mode != Normal ) + { + std::cerr << "Mode not being normal, we're throwing (" << ++throws << " times now)..." << std::endl; + mode= Normal; + throw std::runtime_error{ "Unterminated variable `" + varName.str() + " in expansion." }; + } } }; const auto wrapperIndex= std::ios::xalloc(); void - releaseWrapper( std::ios_base &ios ) + releaseWrapper( std::ostream &os ) { - auto *const streambuf= static_cast< VariableExpansionStreambuf * >( ios.pword( wrapperIndex ) ); + std::cerr << "Release wrapper called on: " << &os << std::endl; + auto *const streambuf= static_cast< VariableExpansionStreambuf * >( os.pword( wrapperIndex ) ); if( not streambuf ) throw std::logic_error{ "Attempt to remove a substitution context which doesn't exist." }; + AutoRAII current + { + [&] { return os.rdbuf( streambuf->underlying ); }, + [&] ( std::streambuf *streambuf ) noexcept + { + std::cerr << "Deletion actually happening, now." << std::endl; + delete streambuf; + os.pword( wrapperIndex )= nullptr; + } + }; streambuf->drain(); - dynamic_cast< std::ostream & >( ios ).rdbuf( streambuf->underlying ); - delete streambuf; - ios.pword( wrapperIndex )= nullptr; } void - wordwrapCallback( const std::ios_base::event event, std::ios_base &ios, const int idx ) + wordwrapCallback( const std::ios_base::event event, std::ios_base &ios, const int idx ) noexcept { + std::cerr << "ios callback called on: " << &ios << std::endl; if( wrapperIndex != idx ) throw std::logic_error{ "Wrong index." }; if( not ios.pword( wrapperIndex ) ) return; - if( event == std::ios_base::erase_event ) releaseWrapper( ios ); + + if( const auto os_p= dynamic_cast< std::ostream * >( &ios ); os_p and event == std::ios_base::erase_event ) + { + releaseWrapper( *os_p ); + } } } @@ -121,6 +142,7 @@ namespace Alepha::Cavorite ::detail:: string_algorithms { state= 1; os.register_callback( wordwrapCallback, wrapperIndex ); + std::cerr << "Adding callback to " << (void *) static_cast< std::ios * >( &os ) << std::endl; } assert( os.pword( wrapperIndex ) == nullptr ); diff --git a/string_algorithms.test/0.cc b/string_algorithms.test/0.cc index 810a7db..ac49301 100644 --- a/string_algorithms.test/0.cc +++ b/string_algorithms.test/0.cc @@ -25,6 +25,11 @@ static auto init= enroll <=[] { "$H$ $W$", { { "H", lambaste<="Hello" }, { "W", lambaste<="World" } }, '$' }, "Hello World" }, + { + "Hello World (with some spaces)", + { "$with space$ $can-expand$", { { "with space", lambaste<="Hello" }, { "can-expand", lambaste<="World" } }, '$' }, + "Hello World" + }, { "Hello $$ World", diff --git a/word_wrap.cpp b/word_wrap.cpp index 7447426..820042a 100644 --- a/word_wrap.cpp +++ b/word_wrap.cpp @@ -111,38 +111,6 @@ namespace Alepha::Cavorite ::detail:: word_wrap return rv; } - namespace - { - const auto wrapperIndex= std::ios_base::xalloc(); - - void - releaseWrapper( std::ios_base &ios ) - { - auto *const streambuf= static_cast< WordWrapStreambuf * >( ios.pword( wrapperIndex ) ); - streambuf->drain(); - dynamic_cast< std::ostream & >( ios ).rdbuf( streambuf->underlying ); - delete streambuf; - ios.pword( wrapperIndex )= nullptr; - } - - void - wordwrapCallback( const std::ios_base::event event, std::ios_base &ios, const int idx ) noexcept - { - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wterminate" - - if( wrapperIndex != idx ) throw std::logic_error( "Must only work with the word wrap index." ); - - if( not ios.pword( wrapperIndex ) ) return; - - if( event == std::ios_base::erase_event ) releaseWrapper( 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 - } - } - void impl::build_streambuf( std::ostream &os, StartWrap &&args ) {