1
0
forked from Alepha/Alepha

Input stream stacking with line numbers!

This should help when building custom language parsers.  An input
stream can be augmented with this stackable streambuf to track
the current line number.  This can (and should) be done low in
the stack, so that any variable expansion and comment stripping
stages will not affect line number count.

The stage bolts on a stream state sidecar to point back to itself.
The observer for the current line peeks into the sidecar to see
the current line number tracking object for the stream and then
gets the current line number from that object.

The line number is the current line the input cursor is on.
Newline characters are treated as-if they're part of the current
line.  The newly created line will start on the first character
after the newline character.  This helps keep line-index counts
accurate too.  (Idea for a further stage?  Line index cursor
too?)
This commit is contained in:
2024-04-03 08:29:10 -04:00
parent 373b07e1c4
commit 7410245314
6 changed files with 292 additions and 2 deletions

View File

@ -30,7 +30,7 @@ namespace Alepha::Hydrogen::IOStreams ::detail:: StackableStreambuf_m
~StackableStreambuf() override;
// Children must be created by `new`.
explicit StackableStreambuf( std::ostream &host );
explicit StackableStreambuf( std::ios &host );
auto out() const { return std::ostream{ underlying }; }
@ -39,7 +39,86 @@ namespace Alepha::Hydrogen::IOStreams ::detail:: StackableStreambuf_m
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;
// EEEW. But to sidestep/circumvent, all this below is necessary.
auto
forwardUnderflow() const
{
int (std::streambuf::*uf)()= &StackableStreambuf::underflow;
return (underlying->*uf)();
}
auto
underlying_eback() const
{
char *(std::streambuf::*eb)() const= &StackableStreambuf::eback;
return (underlying->*eb)();
}
auto
underlying_gptr() const
{
char *(std::streambuf::*gp)() const= &StackableStreambuf::gptr;
return (underlying->*gp)();
}
auto
underlying_egptr() const
{
char *(std::streambuf::*egp)() const= &StackableStreambuf::egptr;
return (underlying->*egp)();
}
void
underlying_setg( char *const b, char *const g, char *const e )
{
void (std::streambuf::*sg)( char *, char *, char * )= &StackableStreambuf::setg;
return (underlying->*sg)( b, g, e );
}
void
assume_underlying()
{
setg( underlying_gptr(), underlying_gptr(), underlying_egptr() );
underlying_setg( egptr(), egptr(), egptr() );
}
};
template< typename T >
@ -57,9 +136,18 @@ namespace Alepha::Hydrogen::IOStreams ::detail:: StackableStreambuf_m
return os;
}
template< typename T >
std::istream &
operator >> ( std::istream &is, PushStack< T > &&params )
{
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 );
}
}