// SPDX-License-Identifier: MIT
#include "Cursor.hh"
#include <fstream>
#include <iostream>
#include <sstream>
#include <stdexcept>
using namespace ::std;
namespace fedem
{
namespace parser
{
namespace
{
constexpr const char EOF_CHAR = '\032'; // asci 032 is ^Z
}
Cursor::Cursor( std::string sourceName )
: filename( make_shared< string >( sourceName ) )
{
line = 0;
column = 0;
newLinePassed = true;
// The whole file is read into memory once. A parser backtracks through
// store()/restore() all the time; on a file stream every tellg() is an
// lseek() and every seekg() drops the stream buffer and re-reads it — a
// 2 KB config file parsed with backtracking cost hundreds of thousands
// of system calls (FFS: ~1 million per new session). On a string
// stream both are plain arithmetic. Parsed inputs are small.
auto buffer = make_shared< istringstream >( );
{
ifstream file( filename->c_str( ), ios::in | ios::binary );
if( file.is_open( ) )
{
ostringstream content;
content << file.rdbuf( );
buffer->str( content.str( ) );
}
else
{
// An unopenable file must leave isValid() false (see Cursor.hh),
// not throw out of the constructor: failbit before the mask is armed.
buffer->setstate( ios::failbit );
}
}
stream = buffer;
if( stream )
{
if( *stream )
{
stream->exceptions( stream_type::failbit | stream_type::eofbit | stream_type::badbit );
stream->unsetf( ios::skipws );
posEnd= false;
store();
}
else
{
posEnd= true;
}
}
else
{
posEnd= true;
}
}
Cursor::Cursor( std::string sourceName, std::shared_ptr< stream_type > externalStream )
: filename( make_shared< string >( sourceName ) ),
stream( externalStream )
{
line = 0;
column = 0;
newLinePassed = true;
if( stream )
{
stream->exceptions( stream_type::failbit | stream_type::eofbit | stream_type::badbit );
stream->unsetf( ios::skipws );
posEnd= false;
store();
}
else
{
posEnd= true;
}
}
Cursor::Cursor( Cursor const& object )
: filename( object.filename ),
stream( object.stream ),
line( object.line ),
column( object.column ),
newLinePassed( object.newLinePassed ),
position( object.position ),
posEnd( object.posEnd )
{
// blank
}
Cursor::~Cursor( )
{
// blank
}
Cursor& Cursor::operator=( Cursor const& object )
{
if( &object != this )
{
filename = object.filename;
stream = object.stream;
line = object.line;
column = object.column;
newLinePassed = object.newLinePassed;
position = object.position;
posEnd = posEnd;
}
return *this;
}
bool Cursor::operator==( Cursor const& object ) const
{
return ( filename == object.getLocation( ) ) && ( line == object.getLineNumber( ) ) && ( column == object.getColumnNumber( ) );
}
void Cursor::rewind( )
{
stream->seekg( 0 );
posEnd = false;
store();
line = 0;
column = 0;
newLinePassed = true;
}
void Cursor::store( ) noexcept
{
try
{
position = stream->tellg( );
posEnd= false;
}
catch( ... )
{
// EOF or stream error: current position is preserved.
posEnd= true;
}
}
void Cursor::restore( )
{
if ( posEnd )
{
stream->seekg( 0, iostream::end );
get( );
}
else
{
stream->seekg( position, iostream::beg );
}
}
void Cursor::safeRestore( ) noexcept
{
try
{
auto const mask = stream->exceptions( );
stream->exceptions( std::ios_base::goodbit );
stream->clear( );
restore();
stream->exceptions( mask );
}
catch( ... )
{
// The stream cannot be recovered; it continues silently.
}
}
bool Cursor::isValid( ) const noexcept
{
return stream && *stream && ! stream->eof( ) && ! stream->bad( );
}
Cursor::stream_type::char_type Cursor::get( )
{
if( ! stream )
{
throw runtime_error( "[Cursor::get] invalid stream" );
}
if( newLinePassed )
{
newLinePassed = false;
++line;
column = 1;
}
else
{
++column;
}
stream_type::int_type read = 0;
read = stream->get( );
newLinePassed = ( read == '\n' );
return static_cast< stream_type::char_type >( read );
}
Cursor::stream_type::char_type Cursor::safeGet( ) noexcept
{
stream_type::int_type read = 0;
if( stream )
{
if( newLinePassed )
{
newLinePassed = false;
++line;
column = 1;
}
else
{
++column;
}
try
{
read = stream->get( );
}
catch( ... )
{
read = EOF_CHAR;
}
newLinePassed = ( read == '\n' );
}
return static_cast< stream_type::char_type >( read );
}
Cursor::stream_type::char_type Cursor::peek( )
{
if( ! stream )
{
throw runtime_error( "[Cursor::peek] invalid stream" );
}
auto const read = stream->peek( );
return read;
}
Cursor::stream_type::char_type Cursor::safePeek( ) noexcept
{
if( ! stream )
return EOF_CHAR;
try
{
stream->clear( stream->rdstate( ) & ~std::ios::eofbit );
auto const read = stream->peek( );
if( stream->eof( ) )
return EOF_CHAR;
return static_cast< stream_type::char_type >( read );
}
catch( ... )
{
return EOF_CHAR;
}
}
} // end namespace parser
} // end namespace fedem