1
0
forked from Alepha/Alepha

IOStream filter stage.

This permits creation of custom filtration stages in the IOStream
streambuf stack.
This commit is contained in:
2024-04-03 23:44:51 -04:00
parent cc1fdf725b
commit 38df35ebc4
4 changed files with 343 additions and 0 deletions

View File

@ -4,6 +4,7 @@ add_subdirectory( streamable.test )
add_subdirectory( LineTrackingStreambuf.test )
add_subdirectory( OutUnixFileBuf.test )
add_subdirectory( StackableStreambuf.test )
add_subdirectory( Filter.test )
target_sources( alepha PRIVATE
StackableStreambuf.cc

213
IOStreams/Filter.h Normal file
View File

@ -0,0 +1,213 @@
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;
}

128
IOStreams/Filter.test/0.cc Normal file
View File

@ -0,0 +1,128 @@
static_assert( __cplusplus > 2020'99 );
#include "../Filter.h"
#include <Alepha/Testing/test.h>
#include <Alepha/Testing/TableTest.h>
#include <sstream>
#include <iostream>
static auto init= Alepha::Utility::enroll <=[]
{
using namespace std::literals::string_literals;
using namespace Alepha::Testing::literals;
using namespace Alepha::Testing::exports;
[]( const std::string &s )
{
std::stringstream iss{ s };
//iss >> Filter{ std::make_unique< CapitalizationFilter >() };
std::string rv;
iss << "bob";
int x;
iss >> x;
};
"Simple Filter"_test <=TableTest
<
[]( const std::string &s )
-> std::string
{
using namespace Alepha::IOStreams::exports::Filter_m;
struct CapitalizationFilter : StreamFilter
{
public:
Emitter
nextChar( const char ch ) override
{
struct EmitterImpl
: public Emitter::Impl
{
char ch;
bool read= false;
explicit EmitterImpl( const char ch ) : ch( ch ) {}
bool empty() const final { return read; }
char
next() final
{
read= true;
std::cerr << "Emitted `" << ch << "`" << std::endl;
return ch;
}
};
std::cerr << "Emitter built for `" << ch << "`" << std::endl;
return Emitter{ std::make_unique< EmitterImpl >( ::toupper( ch ) ) };
}
};
std::ostringstream oss;
oss << Filter{ std::make_unique< CapitalizationFilter >() };
oss << s;
oss << Alepha::IOStreams::PopStack{};
return oss.str();
}
>
::Cases
{
{ "Hello", { "hello" }, "HELLO" },
};
auto streamWriter= []( const std::string &s )
{
using namespace Alepha::IOStreams::exports::Filter_m;
struct CapitalizationFilter : StreamFilter
{
public:
Emitter
nextChar( const char ch ) override
{
struct EmitterImpl
: public Emitter::Impl
{
char ch;
bool read= false;
explicit EmitterImpl( const char ch ) : ch( ch ) {}
bool empty() const final { return read; }
char
next() final
{
read= true;
std::cerr << "Emitted `" << ch << "`" << std::endl;
return ch;
}
};
std::cerr << "Emitter built for `" << ch << "`" << std::endl;
return Emitter{ std::make_unique< EmitterImpl >( ::toupper( ch ) ) };
}
};
std::stringstream iss{ s };
iss >> Filter{ std::make_unique< CapitalizationFilter >() };
std::string rv;
iss >> rv;
iss >> Alepha::IOStreams::PopStack{};
return rv;
};
"Simple Input Filter"_test <=TableTest< streamWriter >::Cases
{
{ "Hello", { "hello" }, "HELLO" },
};
};

View File

@ -0,0 +1 @@
unit_test( 0 )