Code View

cparse / cparse-1.3.0.2 / src / cparse / Cursor.cpp
// 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