Skip to main content

minparser/
pos.rs

1/*
2 * Minparser Simple parsing functions
3 *
4 * Copyright (C) 2024-2025 Paolo De Donato
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
18 */
19//! Objects and utilities to keep track of positions inside a text file.
20//!
21//! A position inside a (textual) file is just a couple of unsigned integers: the *line number* and
22//! the *column number*. Lines (which could be empty) are separated by the `\n` and `\r\n` (on
23//! Windows) sequences of characters. Keeping track of positions is very important in order to help
24//! other people to find where a match failed in a file, so users of this library are encouraged to
25//! sore an istance of [`Position`] in their custom error types.
26use core::convert::{AsRef, AsMut};
27use core::ops::ControlFlow;
28use core::fmt::{Formatter, Display, Debug};
29use core::error::Error;
30use core::write;
31
32/// A position.
33///
34/// Characters in a text file can be organizes in a virtual grid in order to easily find characters
35/// or parsing errors inside the file. Each file can be divided in multiple _lines_ separated by
36/// line feed (U+000A) or carriage return + line feed (U+000D + U+000A).
37///
38/// To find a character then you need just the index of the line containing it (the first line has index `0`) 
39/// and the character index inside thai line (also called the _column_).
40#[derive(Clone, Copy, PartialEq, Eq, Debug, Ord, PartialOrd)]
41pub struct Position {
42    r : u32,
43    c : u32,
44}
45
46impl Position{
47    /// Get the line number
48    #[must_use]
49    pub const fn line(&self) -> u32 {
50        self.r
51    }
52    /// Get the column number
53    #[must_use]
54    pub const fn column(&self) -> u32 {
55        self.c
56    }
57    /// Unpacks the position.
58    ///
59    /// The first integer is the line number, the second one the column number
60    #[must_use]
61    pub const fn unpack(self) -> (u32, u32) {
62        (self.r, self.c)
63    }
64    /// Create a new `Position` at specified `line` and `column` indices.
65    #[must_use]
66    pub const fn new(line : u32, column : u32) -> Self {
67        Self{
68            r : line,
69            c : column
70        }
71    }
72    /// Create a new `Position` with `line=0` and `column=0`.
73    #[must_use]
74    pub const fn new_zero() -> Self {
75        Self::new(0, 0)
76    }
77}
78
79impl Display for Position {
80    fn fmt(&self, fmt : &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
81        write!(fmt, "Line: {}, column: {}", self.r, self.c)
82    }
83}
84impl Default for Position {
85    /// Default position is at `line=0` and `column=0`.
86    fn default() -> Self {
87        Self::new_zero()
88    }
89}
90
91/// A trait for objects with an associated position.
92///
93/// You can freely implement this trait for your objects or instead use [`Pos`] wrapper.
94pub trait Posable{
95    /// Get current position
96    fn get_pos(&self) -> Position;
97
98    /// Get line number
99    fn line(&self) -> u32 {
100        self.get_pos().r
101    }
102    /// Get column number
103    fn column(&self) -> u32 {
104        self.get_pos().c
105    }
106}
107
108/// A trait for _movable_ object, that is you can change their position in a file.
109pub trait PosableMut : Posable {
110    /// Get a mutable reference to its position.
111    ///
112    /// Should return the same position returned by [`get_pos`](Posable::get_pos).
113    fn get_pos_mut(&mut self) -> &mut Position;
114
115    /// Increase or decrease line and column number by an offset.
116    fn progress(&mut self, nrow : i32, ncol : i32) {
117        let pos = self.get_pos_mut();
118        pos.r = pos.r.saturating_add_signed(nrow);
119        pos.c = pos.c.saturating_add_signed(ncol);
120    }
121    /// Sets the position at the beginning of a file.
122    fn seek_start(&mut self) {
123        *self.get_pos_mut() = Position::new_zero();
124    }
125}
126
127impl<T, E> Posable for Result<T, E> where T : Posable, E : Posable {
128    fn get_pos(&self) -> Position {
129        match self {
130            Ok(e) => e.get_pos(),
131            Err(e) => e.get_pos()
132        }
133    }
134}
135impl<T, E> PosableMut for Result<T, E> where T : PosableMut, E : PosableMut {
136    fn get_pos_mut(&mut self) -> &mut Position {
137        match self {
138            Ok(e) => e.get_pos_mut(),
139            Err(e) => e.get_pos_mut()
140        }
141    }
142}
143
144/// A positioned object, functionally equivalent to a struct containing an object of type `T` (the
145/// wrapped object) and its position as a [``Position``] object.
146///
147/// You can use this wrapper for objects that do not implement the [`Posable`] trait.
148#[derive(Clone, Copy, Debug)]
149pub struct Pos<T> {
150    el : T,
151    pos : Position,
152}
153
154impl<T> AsRef<T> for Pos<T> {
155    fn as_ref(&self) -> &T {
156        &self.el
157    }
158}
159impl<T> AsMut<T> for Pos<T> {
160    fn as_mut(&mut self) -> &mut T {
161        &mut self.el
162    }
163}
164
165impl<T> Display for Pos<T> where T : Display {
166    fn fmt(&self, fmt : &mut Formatter<'_>) -> core::fmt::Result {
167        write!(fmt, "{}: {}", self.pos, self.el)
168    }
169}
170
171impl<T> Error for Pos<T> where T : Error {
172    fn source(&self) -> Option<&(dyn Error + 'static)> {
173        self.el.source()
174    }
175}
176
177impl<T> Posable for Pos<T>{
178    fn get_pos(&self) -> Position {
179        self.pos
180    }
181}
182
183impl<T> PosableMut for Pos<T>{
184    fn get_pos_mut(&mut self) -> &mut Position {
185        &mut self.pos
186    }
187}
188
189impl<T> Pos<T> {
190    /// Creates a new positioned object.
191    pub const fn new(el : T, pos : Position) -> Self {
192        Self{el, pos}
193    }
194    /// Creates a new positioned object.
195    pub const fn new_pos(el : T, line : u32, column : u32) -> Self {
196        Self{
197            el,
198            pos : Position{
199                r : line,
200                c : column,
201            }
202        }
203    }
204    /// Consumes the objects and returns the wrapped element
205    pub fn take(self) -> T {
206        self.el
207    }
208    /// Consumes the objects and returns the position
209    pub fn take_pos(self) -> Position {
210        self.pos
211    }
212    /// Consumes the objects and returns both the wrapped element and the position
213    pub fn take_all(self) -> (T, Position) {
214        (self.el, self.pos)
215    }
216
217    /// Tests the wrapped object
218    pub fn test<TF : FnOnce(&T) -> bool>(&self, f : TF) -> bool {
219        f(&self.el)
220    }
221    /// Tests both the wrapped object and its position
222    pub fn test_pos<TF : FnOnce(&T, &Position) -> bool>(&self, f : TF) -> bool {
223        f(&self.el, &self.pos)
224    }
225    /// Apply `f` at the wrapped object
226    pub fn map<U, MF : FnOnce(T) -> U>(self, f : MF) -> Pos<U> {
227        Pos{
228            el : f(self.el),
229            pos : self.pos,
230        }
231    }
232    /// Apply `f` at the wrapped object and its position
233    pub fn map_pos<U, MF : FnOnce(T, Position) -> (U, Position)>(self, f : MF) -> Pos<U> {
234        let (nel, npos) = f(self.el, self.pos);
235        Pos{
236            el : nel,
237            pos : npos,
238        }
239    }
240
241    /// Converts it to a reference
242    pub const fn make_ref(&self) -> Pos<&T> {
243        Pos{
244            el : &(self.el),
245            pos : self.pos,
246        }
247    }
248    /// Converts it to a mutable reference
249    pub const fn make_mut(&mut self) -> Pos<&mut T> {
250        Pos{
251            el : &mut(self.el),
252            pos : self.pos,
253        }
254    }
255}
256
257impl<T, E> Pos<Result<T, E>> {
258    /// Branch.
259    ///
260    /// It could be a suitable implementation for the `Try` trait once it will be stabilized.
261    pub fn branch(self) -> ControlFlow<Pos<Result<core::convert::Infallible, E>>, Pos<T>>{
262        let pos = self.pos;
263        match self.el {
264            Ok(t) => ControlFlow::Continue(Pos{
265                el : t,
266                pos,
267            }),
268            Err(e) => ControlFlow::Break(Pos{
269                el : Err(e),
270                pos,
271            }),
272        }
273    }
274
275    /// Converts a `Pos<Result<T, E>>` into `Result<Pos<T>, Pos<E>>` that can be used with
276    /// the `?` operator 
277    #[allow(clippy::missing_errors_doc)]
278    pub fn throw(self) -> Result<Pos<T>, Pos<E>> {
279        let pos = self.pos;
280        match self.el {
281            Ok(t) => Ok(Pos{
282                el : t,
283                pos,
284            }),
285            Err(e) => Err(Pos{
286                el : e,
287                pos,
288            }),
289        }
290    }
291    /// Converts a `Pos<Result<T, E>>` into `Result<Pos<T>, E>` that can be used with
292    /// the `?` operator 
293    #[allow(clippy::missing_errors_doc)]
294    pub fn throw_el(self) -> Result<Pos<T>, E> {
295        match self.el {
296            Ok(el) => Ok(Pos{el, pos : self.pos}),
297            Err(e) => Err(e),
298        }
299    }
300    /// Converts a `Pos<Result<T, E>>` into `Result<T, Pos<E>>` that can be used with
301    /// the `?` operator 
302    #[allow(clippy::missing_errors_doc)]
303    pub fn throw_err(self) -> Result<T, Pos<E>> {
304        match self.el {
305            Ok(t) => Ok(t),
306            Err(el) => Err(Pos{el, pos : self.pos}),
307        }
308    }
309}
310
311#[cfg(feature = "nightly-features")]
312impl<T> core::ops::Try for Pos<T> where T : core::ops::Try {
313    type Output = Pos<T::Output>;
314    type Residual = Pos<T::Residual>;
315
316    fn from_output(output : Self::Output) -> Self {
317        Self{
318            el : T::from_output(output.el),
319            pos : output.pos
320        }
321    }
322    fn branch(self) -> ControlFlow<Self::Residual, Self::Output> {
323        match self.el.branch() {
324            ControlFlow::Break(e) => ControlFlow::Break(Pos::new(e, self.pos)),
325            ControlFlow::Continue(e) => ControlFlow::Continue(Pos::new(e, self.pos))
326        }
327    }
328}
329#[cfg(feature = "nightly-features")]
330impl<T, R> core::ops::FromResidual<Pos<R>> for Pos<T> where T : core::ops::FromResidual<R> {
331    fn from_residual(output : Pos<R>) -> Self {
332        Self{
333            el : T::from_residual(output.el),
334            pos : output.pos
335        }
336    }
337}
338#[cfg(feature = "nightly-features")]
339impl<T, R> core::ops::Residual<Pos<R>> for Pos<T> where T : core::ops::Residual<R> {
340    type TryType = Pos<T::TryType>;
341}