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

@ -0,0 +1,144 @@
static_assert( __cplusplus > 2020'99 );
#pragma once
#include <Alepha/Alepha.h>
#include <cstdint>
#include <vector>
#include <iostream>
#include <string_view>
#include <algorithm>
#include "StackableStreambuf.h"
#include "StreamState.h"
namespace Alepha::Hydrogen::IOStreams ::detail:: LineTrackingStreambuf_m
{
inline namespace exports {}
struct TrackLines_params {};
namespace exports
{
struct LineTrackingStreambuf;
using TrackLines= IOStreams::PushStack< TrackLines_params >;
std::int64_t getLineNumber( std::ios_base &ios );
auto setLineNumber( std::int64_t baseLine );
}
inline StreamState< LineTrackingStreambuf * > tracker{ []{ return nullptr; } };
struct exports::LineTrackingStreambuf
: public virtual StackableStreambuf, public virtual std::streambuf
{
private:
std::int64_t baseLine= 1;
std::vector< std::int64_t > lineStarts;
char *bufBase= nullptr;
std::int64_t current() const { return gptr() - bufBase; }
std::int64_t
index() const
{
return std::lower_bound( begin( lineStarts ), end( lineStarts ), current() ) - begin( lineStarts );
}
public:
explicit
LineTrackingStreambuf( std::ios &is )
: StackableStreambuf( is )
{
tracker.get( is )= this;
}
void
setLineNumber( const std::int64_t baseLine )
{
this->baseLine= baseLine - index();
}
std::int64_t
getLineNumber() const
{
return baseLine + index();
}
void writeChar( char ch ) override { throw "Unimpl"; }
void drain() override { throw "Unimpl"; }
int
underflow() override
{
//std::cerr << "Present base line is: " << baseLine << std::endl;
//std::cerr << "Present starts count: " << lineStarts.size() << std::endl;
baseLine+= lineStarts.size();
lineStarts.clear();
const auto rv= forwardUnderflow();
//std::cerr << "Underflow char is: " << (char) rv << std::endl;
if( rv == EOF ) return rv;
assume_underlying();
const std::string_view view{ gptr(), egptr() };
bufBase= gptr();
//std::cerr << "Underflow picked up " << view.size() << " chars" << std::endl;;
//std::cerr << "Underflow sees `" << (void*) gptr() << "` for gptr." << std::endl;
for( std::int64_t i= 0; i < view.size(); ++i )
{
if( view.at( i ) == '\n' ) lineStarts.push_back( i + 1 );
}
if( rv == 101 ) abort();
return rv;
}
};
inline std::int64_t
exports::getLineNumber( std::ios_base &ios )
{
auto thisTracker= tracker.get( ios );
if( thisTracker == nullptr ) return -1;
return thisTracker->getLineNumber();
}
#if 0
struct Setter
{
const std::int64_t baseLine;
friend std::istream &
operator >> ( std::istream &is, const Setter &s )
{
auto thisTracker= tracker.get( is );
if( thisTracker != nullptr )
{
thisTracker->setLineNumber( s.baseLine );
}
return is;
}
};
inline auto
exports::setLineNumber( const std::int64_t baseLine )
{
return Setter{ baseLine };
}
#endif
inline void
build_streambuf( std::istream &is, TrackLines && )
{
new LineTrackingStreambuf{ is };
}
}
namespace Alepha::Hydrogen::IOStreams::inline exports::inline LineTrackingStreambuf_m
{
using namespace detail::LineTrackingStreambuf_m::exports;
}