static_assert( __cplusplus > 2020'99 ); #pragma once #include #include #include #include #include namespace Alepha::Hydrogen::IOStreams ::detail:: Filter_m { namespace C { const bool debug= false; } inline namespace exports { class Emitter; Emitter nilEmitter(); class StreamFilter; class FilteringStreambuf; struct Filter_impl { std::unique_ptr< StreamFilter > filter; explicit Filter_impl( std::unique_ptr< StreamFilter > filter ) : filter( std::move( filter ) ) {} }; using Filter= PushStack< Filter_impl >; } // XXX: This should form the new heart of how stackable // streambufs work. One writes StreamFilter objects. // When adding a filter to a streambuf in a stack, we build // a stack of filters. The stackable streambuf becomes // a stackable filter streambuf. class exports::Emitter { public: struct Impl { virtual ~Impl()= default; virtual bool empty() const= 0; virtual char next()= 0; }; private: std::unique_ptr< Impl > pimpl; Impl &impl() { return *pimpl; } const Impl &impl() const { return *pimpl; } public: explicit Emitter( std::unique_ptr< Impl > pimpl ) : pimpl( std::move( pimpl ) ) {} bool empty() const { return impl().empty(); } char next() { return impl().next(); } void drain( std::ostream &out ) { while( not empty() ) { out << next(); } } }; inline Emitter exports::nilEmitter() { struct NilImpl : Emitter::Impl { bool empty() const final { return true; } char next() final { throw std::out_of_range( "" ); } }; return Emitter{ std::make_unique< NilImpl >() }; } class exports::StreamFilter : boost::noncopyable { public: virtual ~StreamFilter()= default; virtual Emitter nextChar( char ch )= 0; virtual Emitter nextChar( std::string_view chars ) { std::size_t amount= 0; for( const char ch: chars ) { ++amount; auto emitter= nextChar( ch ); if( not emitter.empty() ) { return emitter; } } return nilEmitter(); } }; class exports::FilteringStreambuf : public virtual IOStreams::StackableStreambuf { private: static const std::size_t limit= 256; std::unique_ptr< StreamFilter > filter; Emitter nextInput= nilEmitter(); std::string inBuffer; std::string outBuffer= std::string( int( limit ), '\0' ); public: explicit FilteringStreambuf( std::ios &stream, std::unique_ptr< StreamFilter > filter ) : StackableStreambuf( stream ), filter( std::move( filter ) ) {} private: void drain() final {} void writeChar( char ) final { throw "Unimpl"; } int underflow() override { if( C::debug ) error() << "Underflow called..." << std::endl; inBuffer.clear(); while( inBuffer.size() < limit ) { if( nextInput.empty() ) { const auto next= underlying_sbumpc(); if( next == EOF ) break; nextInput= filter->nextChar( next ); continue; } inBuffer.push_back( nextInput.next() ); } setg( inBuffer.data(), inBuffer.data(), inBuffer.data() + inBuffer.size() ); if( inBuffer.empty() ) return EOF; if( C::debug ) error() << "Underflow returning: " << inBuffer.front(); return inBuffer.front(); } int overflow( const int nextChar ) override { sync(); if( nextChar != EOF ) { Emitter emitter= filter->nextChar( nextChar ); emitter.drain( out() ); } return 1; } int sync() override { for( const auto next: std::string_view{ pbase(), pptr() } ) { Emitter emitter= filter->nextChar( next ); emitter.drain( out() ); } out().rdbuf()->pubsync(); std::fill_n( begin( outBuffer ), limit, 0 ); setg( outBuffer.data(), outBuffer.data(), outBuffer.data() + outBuffer.size() ); return 1; } }; inline void build_streambuf( std::ios &ios, Filter &¶ms ) { new FilteringStreambuf( ios, std::move( params.filter ) ); } } namespace Alepha::Hydrogen::IOStreams::inline exports::inline Filter_m { using namespace detail::Filter_m::exports; }