Code View

cparse / cparse-1.3.0.2 / src / cparse / Cursor.hh
// SPDX-License-Identifier: MIT
#pragma once

#include <iostream>
#include <memory>
#include <string>

/// \file Cursor.hh
/// \brief Read-only stream position with line/column tracking and save/restore.

namespace fedem
{
  namespace parser
  {
    /// \brief A read-only position in a character stream, with line and column
    ///        tracking and a save/restore mechanism.
    ///
    /// `Cursor` wraps a `std::istream` (when constructed from a filename, an
    /// in-memory copy of the whole file — so `store()` / `restore()` never
    /// cost a system call — or a caller-supplied stream) and adds:
    ///
    /// - **Line and column counters** advanced by every `get()` / `safeGet()`.
    ///   Position starts at line 0, column 0 ("before any character"); the
    ///   first read moves it to line 1, column 1. `getNextLineNumber()` /
    ///   `getNextColumnNumber()` give the position of the character not yet
    ///   read.
    /// - **A single saved position** — `store()` records `tellg()`, `restore()`
    ///   seeks back to it. Used by `Parser` for backtracking.
    /// - **Throwing and non-throwing accessors.** `get()` / `peek()` throw
    ///   `std::ios_base::failure` at end-of-input (the exception mask is set in
    ///   the constructor); `safeGet()` / `safePeek()` return the sentinel
    ///   `'\032'` (ASCII SUB) instead.
    ///
    /// The stream is shared (`std::shared_ptr`); copying a `Cursor` copies the
    /// position but shares the underlying stream. Move construction and move
    /// assignment are deleted.
    ///
    /// \see https://cparse.fedem.eu/reference/cursor — reference manual page.
    /// \see Parser
    class Cursor
    {
      public:
        /// \brief The underlying stream type (`std::istream`).
        using stream_type = std::istream;

        /// \brief Construct over a file, opened internally for reading.
        /// \param sourceName Path to open. An empty string yields a cursor whose
        ///        stream never becomes valid (`isValid()` stays false).
        /// \note `skipws` is cleared so whitespace is visible to the grammar.
        ///       The exception mask is set to failbit | eofbit | badbit.
        /// \see https://cparse.fedem.eu/reference/cursor-ctor
        explicit Cursor( std::string sourceName = "" );

        /// \brief Construct over a caller-supplied stream.
        /// \param sourceName Name used for diagnostics and `getFilename()`;
        ///        need not be a real path (e.g. `"<memory>"`).
        /// \param externalStream Stream to read from. The cursor shares
        ///        ownership and sets the exception mask and `skipws` on it.
        /// \see https://cparse.fedem.eu/reference/cursor-ctor
        Cursor( std::string sourceName, std::shared_ptr< stream_type > externalStream );

        /// \brief Copy constructor. Copies the position; shares the stream.
        /// \param object Cursor to copy from.
        Cursor( Cursor const& object );

        /// \brief Move construction is deleted.
        Cursor( Cursor&& object ) = delete;

        /// \brief Destructor. Does not close a caller-supplied stream; the
        ///        in-memory copy of a file is freed when its last owner drops.
        /// \see https://cparse.fedem.eu/reference/cursor-dtor
        virtual ~Cursor( );

        /// \brief Copy assignment. Copies the position; shares the stream.
        /// \param object Cursor to copy from.
        /// \return `*this`.
        /// \see https://cparse.fedem.eu/reference/cursor-operator-assign
        Cursor& operator=( Cursor const& object );

        /// \brief Move assignment is deleted.
        Cursor& operator=( Cursor&& object ) = delete;

        /// \brief Equality by location: same filename pointer, line and column.
        ///        The stream contents are not compared.
        /// \param object Cursor to compare against.
        /// \return `true` if both cursors are at the same location.
        /// \see https://cparse.fedem.eu/reference/cursor-operator-eq
        bool operator==( Cursor const& object ) const;

        /// \brief Seek back to the start of the stream and reset the line and
        ///        column counters to zero.
        /// \see https://cparse.fedem.eu/reference/cursor-rewind
        void rewind( );

        /// \brief Record the current stream offset as the saved position.
        ///        If the stream is at or past end-of-input, a flag is set so a
        ///        later `restore()` seeks to the end.
        /// \see https://cparse.fedem.eu/reference/cursor-store
        void store( ) noexcept;

        /// \brief Seek back to the position recorded by the last `store()`.
        /// \note May throw if the stream is in a hard-failed state; use
        ///       `safeRestore()` to recover from that.
        /// \see https://cparse.fedem.eu/reference/cursor-restore
        void restore( );

        /// \brief Clear stream error flags, restore to the saved position, and
        ///        put the exception mask back. Never throws.
        /// \see https://cparse.fedem.eu/reference/cursor-safe-restore
        void safeRestore( ) noexcept;

        /// \brief Whether the stream exists and is readable (not EOF, not bad).
        /// \return `true` while more input can be read.
        /// \see https://cparse.fedem.eu/reference/cursor-is-valid
        bool isValid( ) const noexcept;

        /// \brief Consume and return the next character; advance line/column.
        /// \return The character consumed.
        /// \throws std::runtime_error if the internal stream pointer is null.
        /// \throws std::ios_base::failure at end-of-input or on stream error —
        ///         this is the path `Parser::parse()` uses to detect EOF.
        /// \see https://cparse.fedem.eu/reference/cursor-get
        stream_type::char_type get( );

        /// \brief Non-throwing `get()`: returns `'\032'` at end-of-input
        ///        instead of throwing. Line/column still advance.
        /// \return The character consumed, or `'\032'` at end-of-input.
        /// \see https://cparse.fedem.eu/reference/cursor-safe-get
        stream_type::char_type safeGet( ) noexcept;

        /// \brief Return the next character without consuming it.
        /// \return The next character.
        /// \throws std::runtime_error if the internal stream pointer is null.
        /// \throws std::ios_base::failure at end-of-input or on stream error.
        /// \see https://cparse.fedem.eu/reference/cursor-peek
        stream_type::char_type peek( );

        /// \brief Non-throwing `peek()`: returns `'\032'` at end-of-input
        ///        instead of throwing, and leaves the stream usable.
        /// \return The next character, or `'\032'` at end-of-input.
        /// \see https://cparse.fedem.eu/reference/cursor-safe-peek
        stream_type::char_type safePeek( ) noexcept;

        /// \brief Shared pointer to the source-name string.
        /// \return The shared `std::string` holding the source name; may be null.
        /// \see https://cparse.fedem.eu/reference/cursor-get-location
        std::shared_ptr< std::string > getLocation( ) const noexcept;

        /// \brief The source name.
        /// \return The source name, or an empty string if none was set.
        /// \see https://cparse.fedem.eu/reference/cursor-get-filename
        std::string const& getFilename( ) const noexcept;

        /// \brief The current line number.
        /// \return The 1-based line number once reading has started; 0 before.
        /// \see https://cparse.fedem.eu/reference/cursor-get-line-number
        unsigned long int getLineNumber( ) const noexcept;

        /// \brief The current column number.
        /// \return The 1-based column number once reading has started; 0 before.
        /// \see https://cparse.fedem.eu/reference/cursor-get-column-number
        unsigned long int getColumnNumber( ) const noexcept;

        /// \brief The line of the next character to be read.
        ///
        /// `getLineNumber()` / `getColumnNumber()` name the character read
        /// last; this pair names the one `peek()` would return — where a
        /// diagnostic about input the grammar did not accept belongs. Line 1,
        /// column 1 before any read; the first column of the next line right
        /// after a `'\n'`.
        /// \return The 1-based line number.
        /// \see https://cparse.fedem.eu/reference/cursor-get-next-position
        unsigned long int getNextLineNumber( ) const noexcept;

        /// \brief The column of the next character to be read.
        /// \return The 1-based column number.
        /// \see getNextLineNumber()
        unsigned long int getNextColumnNumber( ) const noexcept;

      private:
        std::shared_ptr< std::string > filename;
        std::shared_ptr< stream_type > stream;
        unsigned long int line;
        unsigned long int column;
        bool newLinePassed;
        std::streampos position;
        bool posEnd;
    };

    inline std::shared_ptr< std::string > Cursor::getLocation( ) const noexcept
    {
      return filename;
    }

    inline std::string const& Cursor::getFilename( ) const noexcept
    {
      static std::string const empty;
      return filename ? *filename : empty;
    }

    inline unsigned long int Cursor::getLineNumber( ) const noexcept
    {
      return line;
    }

    inline unsigned long int Cursor::getColumnNumber( ) const noexcept
    {
      return column;
    }

    inline unsigned long int Cursor::getNextLineNumber( ) const noexcept
    {
      return newLinePassed ? line + 1 : line;
    }

    inline unsigned long int Cursor::getNextColumnNumber( ) const noexcept
    {
      return newLinePassed ? 1 : column + 1;
    }
  }  // end namespace parser
}  // end namespace fedem