forked from Alepha/Alepha
223 lines
5.9 KiB
C++
223 lines
5.9 KiB
C++
static_assert( __cplusplus > 2020'99 );
|
|
|
|
#pragma once
|
|
|
|
#include <Alepha/Alepha.h>
|
|
|
|
#include <ostream>
|
|
#include <exception>
|
|
#include <stdexcept>
|
|
#include <memory>
|
|
#include <stack>
|
|
|
|
#include <Alepha/AutoRAII.h>
|
|
|
|
namespace Alepha::Hydrogen::IOStreams ::detail:: StackableStreambuf_m
|
|
{
|
|
inline namespace exports
|
|
{
|
|
struct StackableStreambuf;
|
|
|
|
template< typename > struct PushStack;
|
|
|
|
struct PopStack{};
|
|
|
|
std::size_t stackDepth( std::ios & );
|
|
}
|
|
|
|
struct StackableStreambufUnderlyingShim
|
|
: protected virtual std::streambuf
|
|
{
|
|
private:
|
|
virtual std::streambuf *getUnderlying() const= 0;
|
|
|
|
protected:
|
|
// EEEW. But to sidestep/circumvent, all this below is necessary.
|
|
auto
|
|
forwardOverflow( const int ch )
|
|
{
|
|
int (std::streambuf::*ov)( int )= &StackableStreambufUnderlyingShim::overflow;
|
|
return ( getUnderlying()->*ov )( ch );
|
|
}
|
|
|
|
auto
|
|
forwardUnderflow()
|
|
{
|
|
int (std::streambuf::*uf)()= &StackableStreambufUnderlyingShim::underflow;
|
|
return (getUnderlying()->*uf)();
|
|
}
|
|
|
|
auto
|
|
underlying_sbumpc() const
|
|
{
|
|
int (std::streambuf::*sbc)()= &StackableStreambufUnderlyingShim::sbumpc;
|
|
return (getUnderlying()->*sbc)();
|
|
}
|
|
|
|
|
|
auto
|
|
underlying_eback() const
|
|
{
|
|
char *(std::streambuf::*eb)() const= &StackableStreambufUnderlyingShim::eback;
|
|
return (getUnderlying()->*eb)();
|
|
}
|
|
|
|
auto
|
|
underlying_gptr() const
|
|
{
|
|
char *(std::streambuf::*gp)() const= &StackableStreambufUnderlyingShim::gptr;
|
|
return (getUnderlying()->*gp)();
|
|
}
|
|
|
|
auto
|
|
underlying_egptr() const
|
|
{
|
|
char *(std::streambuf::*egp)() const= &StackableStreambufUnderlyingShim::egptr;
|
|
return (getUnderlying()->*egp)();
|
|
}
|
|
|
|
void
|
|
underlying_setg( char *const b, char *const g, char *const e )
|
|
{
|
|
void (std::streambuf::*sg)( char *, char *, char * )= &StackableStreambufUnderlyingShim::setg;
|
|
return (getUnderlying()->*sg)( b, g, e );
|
|
}
|
|
|
|
|
|
auto
|
|
underlying_pbase() const
|
|
{
|
|
char *(std::streambuf::*pb)() const= &StackableStreambufUnderlyingShim::pbase;
|
|
return (getUnderlying()->*pb)();
|
|
}
|
|
|
|
auto
|
|
underlying_pptr() const
|
|
{
|
|
char *(std::streambuf::*pp)() const= &StackableStreambufUnderlyingShim::pptr;
|
|
return (getUnderlying()->*pp)();
|
|
}
|
|
|
|
auto
|
|
underlying_epptr() const
|
|
{
|
|
char *(std::streambuf::*ep)() const= &StackableStreambufUnderlyingShim::epptr;
|
|
return (getUnderlying()->*ep)();
|
|
}
|
|
};
|
|
|
|
struct exports::StackableStreambuf
|
|
: virtual public std::streambuf, virtual protected StackableStreambufUnderlyingShim
|
|
{
|
|
private:
|
|
std::streambuf *underlying;
|
|
std::streambuf *getUnderlying() const final { return underlying; }
|
|
|
|
public:
|
|
~StackableStreambuf() override;
|
|
|
|
// Children must be created by `new`.
|
|
explicit StackableStreambuf( std::ios &host );
|
|
|
|
virtual void writeChar( char ch )= 0;
|
|
virtual void drain()= 0;
|
|
|
|
void unhook( std::ostream &os ) const;
|
|
void unhook( std::istream &is ) const;
|
|
|
|
protected:
|
|
std::ostream &out( std::ostream &&res= std::ostream{ nullptr } ) const;
|
|
|
|
int overflow( const int ch ) override;
|
|
|
|
// Underflow is not overridden. A read-oriented
|
|
// streambuf stack may choose to implement underflow.
|
|
//
|
|
// The reason underflow is not adapted to a
|
|
// char-interceptor is that memory usage would become
|
|
// unbound. For overflow/writing, we can assume
|
|
// that sufficient output storage space exists for
|
|
// whatever transformations a single char turns into.
|
|
// For instance, a decompression algorithm might
|
|
// cause several kilobytes of data to be generated
|
|
// once a single character is written. These
|
|
// kilobytes must be immediately stored deeper into
|
|
// the stack. These writes can be fire-and-forget,
|
|
// as each lower level will have to deal with and
|
|
// store those bytes (and potential further
|
|
// expansions or reductions). Writes are conveniently
|
|
// fire-and-forget.
|
|
//
|
|
// Whereas, for input, when converting a single input
|
|
// char from the lower level, a reduction would simply
|
|
// require subsequent reads proxied by the stack.
|
|
// However, expansions would potentially emit large
|
|
// blocks of data which would have to be cached
|
|
// somewhere. The intermediate state of the expansion
|
|
// generator cannot be suspended and resumed -- it's
|
|
// a function, not a coroutine. Thus, the entirety
|
|
// of the expansion routine has to be drained into
|
|
// a lookaside buffer to permit it to run to completion.
|
|
// This buffer has to be held in memory, which can
|
|
// grow without bound. Whereas, with writes, we can
|
|
// block this generator on downstream space being
|
|
// available. Yet reads require the upstream has
|
|
// to be given a chance to handle what we've seen so
|
|
// far in order to make more in-memory buffer space
|
|
// available.
|
|
|
|
std::streamsize xsputn( const char *data, std::streamsize amt ) override;
|
|
void
|
|
assume_underlying()
|
|
{
|
|
setg( underlying_gptr(), underlying_gptr(), underlying_egptr() );
|
|
underlying_setg( egptr(), egptr(), egptr() );
|
|
}
|
|
};
|
|
|
|
template< typename T >
|
|
struct exports::PushStack
|
|
: T
|
|
{
|
|
using T::T;
|
|
};
|
|
|
|
template< typename T >
|
|
std::ostream &
|
|
operator << ( std::ostream &os, PushStack< T > &¶ms )
|
|
{
|
|
build_streambuf( os, std::move( params ) );
|
|
return os;
|
|
}
|
|
|
|
template< typename T >
|
|
std::istream &
|
|
operator >> ( std::istream &is, PushStack< T > &¶ms )
|
|
{
|
|
build_streambuf( is, std::move( params ) );
|
|
return is;
|
|
}
|
|
|
|
inline namespace impl
|
|
{
|
|
std::ostream & operator << ( std::ostream &os, PopStack );
|
|
std::istream & operator >> ( std::istream &is, PopStack );
|
|
}
|
|
|
|
template< typename T >
|
|
[[nodiscard]] auto
|
|
adaptStream( PushStack< T > &¶ms, std::ostream &os )
|
|
{
|
|
return AutoRAII
|
|
{
|
|
[&os, ¶ms] { build_streambuf( os, std::move( params ) ); },
|
|
[&os] { os << PopStack{}; }
|
|
};
|
|
}
|
|
}
|
|
|
|
namespace Alepha::Hydrogen::IOStreams::inline exports::inline StackableStreambuf_m
|
|
{
|
|
using namespace detail::StackableStreambuf_m::exports;
|
|
}
|