// 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