minparser 0.13.4

Simple parsing functions
Documentation
/*
 * Minparser Simple parsing functions
 *
 * Copyright (C) 2024-2025 Paolo De Donato
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
//! Objects and utilities to keep track of positions inside a text file.
//!
//! A position inside a (textual) file is just a couple of unsigned integers: the *line number* and
//! the *column number*. Lines (which could be empty) are separated by the `\n` and `\r\n` (on
//! Windows) sequences of characters. Keeping track of positions is very important in order to help
//! other people to find where a match failed in a file, so users of this library are encouraged to
//! sore an istance of [`Position`] in their custom error types.
use core::convert::{AsRef, AsMut};
use core::ops::ControlFlow;
use core::fmt::{Formatter, Display, Debug};
use core::error::Error;
use core::write;

/// A position.
///
/// Characters in a text file can be organizes in a virtual grid in order to easily find characters
/// or parsing errors inside the file. Each file can be divided in multiple _lines_ separated by
/// line feed (U+000A) or carriage return + line feed (U+000D + U+000A).
///
/// To find a character then you need just the index of the line containing it (the first line has index `0`) 
/// and the character index inside thai line (also called the _column_).
#[derive(Clone, Copy, PartialEq, Eq, Debug, Ord, PartialOrd)]
pub struct Position {
    r : u32,
    c : u32,
}

impl Position{
    /// Get the line number
    #[must_use]
    pub const fn line(&self) -> u32 {
        self.r
    }
    /// Get the column number
    #[must_use]
    pub const fn column(&self) -> u32 {
        self.c
    }
    /// Unpacks the position.
    ///
    /// The first integer is the line number, the second one the column number
    #[must_use]
    pub const fn unpack(self) -> (u32, u32) {
        (self.r, self.c)
    }
    /// Create a new `Position` at specified `line` and `column` indices.
    #[must_use]
    pub const fn new(line : u32, column : u32) -> Self {
        Self{
            r : line,
            c : column
        }
    }
    /// Create a new `Position` with `line=0` and `column=0`.
    #[must_use]
    pub const fn new_zero() -> Self {
        Self::new(0, 0)
    }
}

impl Display for Position {
    fn fmt(&self, fmt : &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        write!(fmt, "Line: {}, column: {}", self.r, self.c)
    }
}
impl Default for Position {
    /// Default position is at `line=0` and `column=0`.
    fn default() -> Self {
        Self::new_zero()
    }
}

/// A trait for objects with an associated position.
///
/// You can freely implement this trait for your objects or instead use [`Pos`] wrapper.
pub trait Posable{
    /// Get current position
    fn get_pos(&self) -> Position;

    /// Get line number
    fn line(&self) -> u32 {
        self.get_pos().r
    }
    /// Get column number
    fn column(&self) -> u32 {
        self.get_pos().c
    }
}

/// A trait for _movable_ object, that is you can change their position in a file.
pub trait PosableMut : Posable {
    /// Get a mutable reference to its position.
    ///
    /// Should return the same position returned by [`get_pos`](Posable::get_pos).
    fn get_pos_mut(&mut self) -> &mut Position;

    /// Increase or decrease line and column number by an offset.
    fn progress(&mut self, nrow : i32, ncol : i32) {
        let pos = self.get_pos_mut();
        pos.r = pos.r.saturating_add_signed(nrow);
        pos.c = pos.c.saturating_add_signed(ncol);
    }
    /// Sets the position at the beginning of a file.
    fn seek_start(&mut self) {
        *self.get_pos_mut() = Position::new_zero();
    }
}

impl<T, E> Posable for Result<T, E> where T : Posable, E : Posable {
    fn get_pos(&self) -> Position {
        match self {
            Ok(e) => e.get_pos(),
            Err(e) => e.get_pos()
        }
    }
}
impl<T, E> PosableMut for Result<T, E> where T : PosableMut, E : PosableMut {
    fn get_pos_mut(&mut self) -> &mut Position {
        match self {
            Ok(e) => e.get_pos_mut(),
            Err(e) => e.get_pos_mut()
        }
    }
}

/// A positioned object, functionally equivalent to a struct containing an object of type `T` (the
/// wrapped object) and its position as a [``Position``] object.
///
/// You can use this wrapper for objects that do not implement the [`Posable`] trait.
#[derive(Clone, Copy, Debug)]
pub struct Pos<T> {
    el : T,
    pos : Position,
}

impl<T> AsRef<T> for Pos<T> {
    fn as_ref(&self) -> &T {
        &self.el
    }
}
impl<T> AsMut<T> for Pos<T> {
    fn as_mut(&mut self) -> &mut T {
        &mut self.el
    }
}

impl<T> Display for Pos<T> where T : Display {
    fn fmt(&self, fmt : &mut Formatter<'_>) -> core::fmt::Result {
        write!(fmt, "{}: {}", self.pos, self.el)
    }
}

impl<T> Error for Pos<T> where T : Error {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        self.el.source()
    }
}

impl<T> Posable for Pos<T>{
    fn get_pos(&self) -> Position {
        self.pos
    }
}

impl<T> PosableMut for Pos<T>{
    fn get_pos_mut(&mut self) -> &mut Position {
        &mut self.pos
    }
}

impl<T> Pos<T> {
    /// Creates a new positioned object.
    pub const fn new(el : T, pos : Position) -> Self {
        Self{el, pos}
    }
    /// Creates a new positioned object.
    pub const fn new_pos(el : T, line : u32, column : u32) -> Self {
        Self{
            el,
            pos : Position{
                r : line,
                c : column,
            }
        }
    }
    /// Consumes the objects and returns the wrapped element
    pub fn take(self) -> T {
        self.el
    }
    /// Consumes the objects and returns the position
    pub fn take_pos(self) -> Position {
        self.pos
    }
    /// Consumes the objects and returns both the wrapped element and the position
    pub fn take_all(self) -> (T, Position) {
        (self.el, self.pos)
    }

    /// Tests the wrapped object
    pub fn test<TF : FnOnce(&T) -> bool>(&self, f : TF) -> bool {
        f(&self.el)
    }
    /// Tests both the wrapped object and its position
    pub fn test_pos<TF : FnOnce(&T, &Position) -> bool>(&self, f : TF) -> bool {
        f(&self.el, &self.pos)
    }
    /// Apply `f` at the wrapped object
    pub fn map<U, MF : FnOnce(T) -> U>(self, f : MF) -> Pos<U> {
        Pos{
            el : f(self.el),
            pos : self.pos,
        }
    }
    /// Apply `f` at the wrapped object and its position
    pub fn map_pos<U, MF : FnOnce(T, Position) -> (U, Position)>(self, f : MF) -> Pos<U> {
        let (nel, npos) = f(self.el, self.pos);
        Pos{
            el : nel,
            pos : npos,
        }
    }

    /// Converts it to a reference
    pub const fn make_ref(&self) -> Pos<&T> {
        Pos{
            el : &(self.el),
            pos : self.pos,
        }
    }
    /// Converts it to a mutable reference
    pub const fn make_mut(&mut self) -> Pos<&mut T> {
        Pos{
            el : &mut(self.el),
            pos : self.pos,
        }
    }
}

impl<T, E> Pos<Result<T, E>> {
    /// Branch.
    ///
    /// It could be a suitable implementation for the `Try` trait once it will be stabilized.
    pub fn branch(self) -> ControlFlow<Pos<Result<core::convert::Infallible, E>>, Pos<T>>{
        let pos = self.pos;
        match self.el {
            Ok(t) => ControlFlow::Continue(Pos{
                el : t,
                pos,
            }),
            Err(e) => ControlFlow::Break(Pos{
                el : Err(e),
                pos,
            }),
        }
    }

    /// Converts a `Pos<Result<T, E>>` into `Result<Pos<T>, Pos<E>>` that can be used with
    /// the `?` operator 
    #[allow(clippy::missing_errors_doc)]
    pub fn throw(self) -> Result<Pos<T>, Pos<E>> {
        let pos = self.pos;
        match self.el {
            Ok(t) => Ok(Pos{
                el : t,
                pos,
            }),
            Err(e) => Err(Pos{
                el : e,
                pos,
            }),
        }
    }
    /// Converts a `Pos<Result<T, E>>` into `Result<Pos<T>, E>` that can be used with
    /// the `?` operator 
    #[allow(clippy::missing_errors_doc)]
    pub fn throw_el(self) -> Result<Pos<T>, E> {
        match self.el {
            Ok(el) => Ok(Pos{el, pos : self.pos}),
            Err(e) => Err(e),
        }
    }
    /// Converts a `Pos<Result<T, E>>` into `Result<T, Pos<E>>` that can be used with
    /// the `?` operator 
    #[allow(clippy::missing_errors_doc)]
    pub fn throw_err(self) -> Result<T, Pos<E>> {
        match self.el {
            Ok(t) => Ok(t),
            Err(el) => Err(Pos{el, pos : self.pos}),
        }
    }
}

#[cfg(feature = "nightly-features")]
impl<T> core::ops::Try for Pos<T> where T : core::ops::Try {
    type Output = Pos<T::Output>;
    type Residual = Pos<T::Residual>;

    fn from_output(output : Self::Output) -> Self {
        Self{
            el : T::from_output(output.el),
            pos : output.pos
        }
    }
    fn branch(self) -> ControlFlow<Self::Residual, Self::Output> {
        match self.el.branch() {
            ControlFlow::Break(e) => ControlFlow::Break(Pos::new(e, self.pos)),
            ControlFlow::Continue(e) => ControlFlow::Continue(Pos::new(e, self.pos))
        }
    }
}
#[cfg(feature = "nightly-features")]
impl<T, R> core::ops::FromResidual<Pos<R>> for Pos<T> where T : core::ops::FromResidual<R> {
    fn from_residual(output : Pos<R>) -> Self {
        Self{
            el : T::from_residual(output.el),
            pos : output.pos
        }
    }
}
#[cfg(feature = "nightly-features")]
impl<T, R> core::ops::Residual<Pos<R>> for Pos<T> where T : core::ops::Residual<R> {
    type TryType = Pos<T::TryType>;
}