Code View

cparse / cparse-1.3.0.2 / src / cparse / Parser.cpp
// SPDX-License-Identifier: MIT
#include "Parser.hh"

#include <cctype>
#include <exception>
#include <filesystem>
#include <iostream>
#include <stdexcept>
#include <system_error>

#include "Cursor.hh"

using namespace std;

namespace fedem
{
  namespace parser
  {
    Parser::Parser( ) = default;
    Parser::~Parser( ) = default;

    // Resolve `filename` to a canonical path and point `cursor` at a freshly
    // opened Cursor for it. Shared by parse() and openStream() — both need
    // exactly this and nothing more; what happens after (run start(), or
    // just stop and leave the cursor for the caller to drive) is where they
    // diverge. `caller` names the public entry point for the clog tag
    // ("[Parser::parse]" / "[Parser::openStream]") — kept distinct rather
    // than a single generic tag so a diagnostic still points at which one
    // was actually called; ParserTest.ParseMissingFile pins parse()'s.
    bool Parser::openCursorFor( string const& filename, string const& caller )
    {
      filesystem::path absolutePath;
      try
      {
        absolutePath = filesystem::canonical( filename );
        cursor = make_shared< Cursor >( absolutePath.string( ) );
        return true;
      }
      catch( filesystem::filesystem_error const& e )
      {
        // Infrastructure error — file not found / permission denied.
        // Use e.what() which contains the full descriptive message.
        // Not a grammar error, so goes to clog (not the error collector).
        clog << "[Parser::" << caller << "] filesystem error: cannot make canonical path: "
             << e.code( ).message( ) << " [" << filename << "]\n";
      }
      catch( ios_base::failure const& e )
      {
        if( e.code( ).category( ) == iostream_category( ) && e.code( ).value( ) == 1 )
          clog << "[Parser::" << caller << "] file not found '" << absolutePath << "'\n";
        else
          clog << "[Parser::" << caller << "] I/O failure: " << e.code( ).message( ) << "\n";
      }
      catch( exception const& ex )
      {
        clog << "[Parser::" << caller << "] " << ex.what( ) << '\n';
      }
      catch( ... )
      {
        clog << "[Parser::" << caller << "] undefined error";
      }
      return false;
    }

    bool Parser::openStream( string filename )
    {
      clearErrors( );

      if( !cursor )
        openScopes_ = 0U;

      if( cursor )
      {
        cursor->store( );
        returnPoints.push( cursor );
      }

      // Deliberately stops here: no start(), no teardown. See the doc
      // comment in Parser.hh for why parse() cannot be reused for this.
      return openCursorFor( filename, "openStream" );
    }

    bool Parser::parse( string filename )
    {
      bool result = false;

      clearErrors( );

      // Only the OUTERMOST parse resets the scope depth. A nested parse()
      // is an include: the including grammar legitimately has scopes open
      // around it, and clearing the count here would hide a truncated
      // include behind its parent's still-open braces.
      if( !cursor )
        openScopes_ = 0U;

      if( cursor )
      {
        cursor->store( );
        returnPoints.push( cursor );
      }

      result = openCursorFor( filename, "parse" );

      if( result )
      {
        result = false;
        try
        {
          result = start( );

          if( !result )
          {
            // start() returned false — grammar signalled failure without exit()
            recordError( ParseError::Kind::Internal,
                         "unexpected file parsing result" );
          }
          else if( !cursor->isValid( ) || !returnPoints.empty( ) )
          {
            // Cursor is invalid (EOF reached) or there are nested parse contexts.
            // For a full-file parser (declaration list) this is the normal
            // successful ending — the EOF exception fires and sets result=true
            // via the ios_base::failure handler below.  If we reach here, the
            // stream ended cleanly without throwing, which is also acceptable.
            // Demote to Warning so callers can distinguish from real failures.
            if( !returnPoints.empty( ) )
            {
              recordError( ParseError::Kind::Warning,
                           "parser returned with unclosed cursor stack" );
              result = false;
            }
            // else: cursor simply reached EOF cleanly — leave result=true.
          }
        }
        catch( Exited const& )
        {
          // A fatal grammar error was recorded via recordError() before
          // exit() was called.  Clear the cursor stack and return false.
          while( !returnPoints.empty( ) )
            returnPoints.pop( );
          result = false;
        }
        catch( ios_base::failure const& e )
        {
          if( e.code( ).category( ) != iostream_category( ) || e.code( ).value( ) != 1 )
          {
            recordError( ParseError::Kind::Internal,
                         "I/O failure: " + e.code( ).message( ) );
          }
          else if( returnPoints.empty( ) )
          {
            // End of input. Whether that is success depends on what the
            // grammar still had open — see enterScope() in the header.
            if( openScopes_ > 0U )
            {
              recordError( ParseError::Kind::Syntax,
                           "unexpected end of input: "
                           + std::to_string( openScopes_ )
                           + " unterminated scope(s)" );
              result = false;
            }
            else
            {
              result = true;   // normal EOF
            }
          }
        }
        catch( runtime_error& error )
        {
          recordError( ParseError::Kind::Internal, error.what( ) );
        }
        catch( int& code )
        {
          recordError( ParseError::Kind::Internal,
                       "error code " + to_string( code ) );
        }
        catch( ... )
        {
          recordError( ParseError::Kind::Internal, "undefined error" );
        }
      }

      if( returnPoints.empty( ) )
        cursor.reset( );
      else
      {
        cursor = returnPoints.top( );
        returnPoints.pop( );
        cursor->restore( );
        result = true;
      }

      return result;
    }

    // ── open-scope tracking ────────────────────────────────────────────
    // See Parser.hh for why this is not an RAII guard.

    void Parser::enterScope( ) noexcept
    {
      ++openScopes_;
    }

    void Parser::leaveScope( ) noexcept
    {
      if( openScopes_ > 0U )
        --openScopes_;
    }

    unsigned int Parser::openScopeDepth( ) const noexcept
    {
      return openScopes_;
    }

    void Parser::recordError(
      ParseError::Kind   kind,
      string const&      message,
      unsigned long int  line,
      unsigned long int  col )
    {
      ParseError e;
      e.kind     = kind;
      e.message  = message;
      e.filename = cursor ? cursor->getFilename( ) : "";
      e.line     = ( line != 0 ) ? line
                 : ( cursor   ? cursor->getNextLineNumber( )   : 0 );
      e.col      = ( col  != 0 ) ? col
                 : ( cursor   ? cursor->getNextColumnNumber( ) : 0 );
      errors_.push_back( std::move( e ) );
    }

    void Parser::writeTraceMessage( )
    {
      clog << " in file \"" << cursor->getFilename( )
           << "\" at line " << cursor->getLineNumber( )
           << " and column " << cursor->getColumnNumber( ) << endl;

      unsigned int count = 0;
      unsigned long int line = 0;

      Cursor::stream_type::char_type read = cursor->get( );

      while( ( line < 3U ) && cursor->isValid( ) )
      {
        if( ( count > 120U ) || ( read == '\n' ) )
        {
          ++line;
          count = 0;
        }
        clog << ( isprint( static_cast< unsigned char >( read ) ) || read == '\n' ? read : ' ' );
        read = cursor->get( );
        ++count;
      }
      clog << endl;
    }

    bool Parser::skipWhiteSpaces( )
    {
      bool const whiteSpace = isspace( static_cast< unsigned char >( cursor->safePeek( ) ) );
      if( whiteSpace )
      {
        try
        {
          do
          {
            cursor->get( );
          } while( isspace( static_cast< unsigned char >( cursor->peek( ) ) ) );
        }
        catch( ... )
        {
          // EOF or stream failure — normal
        }
      }
      return whiteSpace;
    }

    bool Parser::skipComments( )
    {
      return false;
    }

    bool Parser::skipCommentsBlock( )
    {
      bool result = false;
      bool remark = false;
      do
      {
        bool whiteSpace = skipWhiteSpaces( );
        remark = skipComments( );
        result = result || whiteSpace || remark;
      } while( remark );
      return result;
    }

    bool Parser::extractToken( string const& token )
    {
      bool matched = false;

      if( cursor->isValid( ) && !token.empty( ) )
      {
        Cursor save = storeCursor( );

        auto pos = token.cbegin( );
        while( pos != token.cend( ) && *pos == cursor->safePeek( ) )
        {
          cursor->get( );
          ++pos;
        }

        matched = ( pos == token.cend( ) );

        if( !matched )
          restoreCursor( save );
      }

      return matched;
    }

    bool Parser::extractCharacter( string::value_type const token )
    {
      auto read = cursor->safePeek( );
      bool matched = token == read;
      if( matched )
        cursor->get( );
      return matched;
    }

    void Parser::exit( int status )
    {
      Exited exitException( status );
      throw exitException;
    }

    void Parser::pushCursor( string const& sourceName,
                             shared_ptr< Cursor::stream_type > stream )
    {
      if( cursor )
      {
        cursor->store( );
        returnPoints.push( cursor );
      }
      cursor = make_shared< Cursor >( sourceName, stream );
    }

    Cursor const& Parser::storeCursor( )
    {
      cursor->store( );
      return *cursor;
    }

    void Parser::restoreCursor( Cursor const& crs )
    {
      *cursor = crs;
      cursor->restore( );
    }

    Parser::Exited::Exited( int code )
    {
      exitCode = code;
    }

    int Parser::Exited::getExitCode( ) const
    {
      return exitCode;
    }

  }  // end namespace parser
}  // end namespace fedem