1
0
forked from Alepha/Alepha
Files
Alepha/IOStreams/Filter.h
ADAM David Alan Martin 38df35ebc4 IOStream filter stage.
This permits creation of custom filtration stages in the IOStream
streambuf stack.
2024-04-03 23:44:51 -04:00

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 &&params )
{
new FilteringStreambuf( ios, std::move( params.filter ) );
}
}
namespace Alepha::Hydrogen::IOStreams::inline exports::inline Filter_m
{
using namespace detail::Filter_m::exports;
}