forked from Alepha/Alepha
This permits creation of custom filtration stages in the IOStream streambuf stack.
214 lines
4.1 KiB
C++
214 lines
4.1 KiB
C++
static_assert( __cplusplus > 2020'99 );
|
|
|
|
#pragma once
|
|
|
|
#include <Alepha/Alepha.h>
|
|
|
|
#include <memory>
|
|
|
|
#include <boost/noncopyable.hpp>
|
|
|
|
#include <Alepha/error.h>
|
|
|
|
#include <Alepha/IOStreams/StackableStreambuf.h>
|
|
|
|
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;
|
|
}
|
|
|