Skip to main content

rhai/types/
position.rs

1//! Script character position type.
2#![cfg(not(feature = "no_position"))]
3
4#[cfg(feature = "no_std")]
5use std::prelude::v1::*;
6use std::{
7    fmt,
8    ops::{Add, AddAssign},
9};
10
11/// A location (line number + character position) in the input script.
12///
13/// # Limitations
14///
15/// In order to keep footprint small, both line number and character position have 16-bit resolution,
16/// meaning they go up to a maximum of 65,535 lines and 65,535 characters per line.
17///
18/// Advancing beyond the maximum line length or maximum number of lines is not an error but has no effect.
19#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)]
20pub struct Position {
21    /// Line number: 0 = none
22    line: u16,
23    /// Character position: 0 = BOL
24    pos: u16,
25}
26
27impl Position {
28    /// A [`Position`] representing no position.
29    pub const NONE: Self = Self { line: 0, pos: 0 };
30    /// A [`Position`] representing the first position.
31    pub const START: Self = Self { line: 1, pos: 0 };
32
33    /// Create a new [`Position`].
34    ///
35    /// `line` must not be zero.
36    ///
37    /// If `position` is zero, then it is at the beginning of a line.
38    ///
39    /// # Panics
40    ///
41    /// Panics if `line` is zero.
42    #[inline]
43    #[must_use]
44    pub const fn new(line: u16, position: u16) -> Self {
45        assert!(line != 0, "line cannot be zero");
46
47        let _pos = position;
48
49        Self { line, pos: _pos }
50    }
51    /// Get the line number (1-based), or [`None`] if there is no position.
52    ///
53    /// Always returns [`None`] under `no_position`.
54    #[inline]
55    #[must_use]
56    pub const fn line(self) -> Option<usize> {
57        if self.is_none() {
58            None
59        } else {
60            Some(self.line as usize)
61        }
62    }
63    /// Get the character position (1-based), or [`None`] if at beginning of a line.
64    ///
65    /// Always returns [`None`] under `no_position`.
66    #[inline]
67    #[must_use]
68    pub const fn position(self) -> Option<usize> {
69        if self.is_none() || self.pos == 0 {
70            None
71        } else {
72            Some(self.pos as usize)
73        }
74    }
75    /// Advance by one character position.
76    #[inline]
77    pub(crate) fn advance(&mut self) {
78        assert!(!self.is_none(), "cannot advance Position::NONE");
79        self.pos = self.pos.saturating_add(1);
80    }
81    /// Go backwards by one character position.
82    ///
83    /// # Panics
84    ///
85    /// Panics if already at beginning of a line - cannot rewind to a previous line.
86    #[inline]
87    pub(crate) fn rewind(&mut self) {
88        assert!(!self.is_none(), "cannot rewind Position::NONE");
89        assert!(self.pos > 0, "cannot rewind at position 0");
90        self.pos = self.pos.saturating_sub(1);
91    }
92    /// Advance to the next line.
93    #[inline]
94    pub(crate) fn new_line(&mut self) {
95        assert!(!self.is_none(), "cannot advance Position::NONE");
96
97        // Advance up to maximum position
98        self.line = self.line.saturating_add(1);
99        self.pos = 0;
100    }
101    /// Is this [`Position`] at the beginning of a line?
102    ///
103    /// Always returns `false` under `no_position`.
104    #[inline]
105    #[must_use]
106    pub const fn is_beginning_of_line(self) -> bool {
107        self.pos == 0 && !self.is_none()
108    }
109    /// Is there no [`Position`]?
110    ///
111    /// Always returns `true` under `no_position`.
112    #[inline]
113    #[must_use]
114    pub const fn is_none(self) -> bool {
115        self.line == 0 && self.pos == 0
116    }
117    /// Returns an fallback [`Position`] if it is [`NONE`][Position::NONE]?
118    ///
119    /// Always returns the fallback under `no_position`.
120    #[inline]
121    #[must_use]
122    pub const fn or_else(self, pos: Self) -> Self {
123        if self.is_none() {
124            pos
125        } else {
126            self
127        }
128    }
129}
130
131impl Default for Position {
132    #[inline(always)]
133    fn default() -> Self {
134        Self::START
135    }
136}
137
138impl fmt::Display for Position {
139    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140        if self.is_none() {
141            write!(f, "none")
142        } else {
143            write!(f, "line {}, position {}", self.line, self.pos)
144        }
145    }
146}
147
148impl fmt::Debug for Position {
149    #[cold]
150    #[inline(never)]
151    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
152        if self.is_none() {
153            f.write_str("none")
154        } else if self.is_beginning_of_line() {
155            write!(f, "{}", self.line)
156        } else {
157            write!(f, "{}:{}", self.line, self.pos)
158        }
159    }
160}
161
162impl Add for Position {
163    type Output = Self;
164
165    fn add(self, rhs: Self) -> Self::Output {
166        if rhs.is_none() {
167            self
168        } else {
169            Self {
170                line: self.line + rhs.line - 1,
171                pos: if rhs.is_beginning_of_line() {
172                    self.pos
173                } else {
174                    self.pos + rhs.pos - 1
175                },
176            }
177        }
178    }
179}
180
181impl AddAssign for Position {
182    fn add_assign(&mut self, rhs: Self) {
183        *self = *self + rhs;
184    }
185}
186
187/// _(internals)_ A span consisting of a starting and an ending [positions][Position].
188/// Exported under the `internals` feature only.
189#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)]
190pub struct Span {
191    /// Starting [position][Position].
192    start: Position,
193    /// Ending [position][Position].
194    end: Position,
195}
196
197impl Default for Span {
198    #[inline(always)]
199    fn default() -> Self {
200        Self::NONE
201    }
202}
203
204impl Span {
205    /// Empty [`Span`].
206    pub const NONE: Self = Self::new(Position::NONE, Position::NONE);
207
208    /// Create a new [`Span`].
209    #[inline(always)]
210    #[must_use]
211    pub const fn new(start: Position, end: Position) -> Self {
212        Self { start, end }
213    }
214    /// Is this [`Span`] non-existent?
215    ///
216    /// Always returns `true` under `no_position`.
217    #[inline(always)]
218    #[must_use]
219    pub const fn is_none(self) -> bool {
220        self.start.is_none() && self.end.is_none()
221    }
222    /// Get the [`Span`]'s starting [position][Position].
223    ///
224    /// Always returns [`Position::NONE`] under `no_position`.
225    #[inline(always)]
226    #[must_use]
227    pub const fn start(self) -> Position {
228        self.start
229    }
230    /// Get the [`Span`]'s ending [position][Position].
231    ///
232    /// Always returns [`Position::NONE`] under `no_position`.
233    #[inline(always)]
234    #[must_use]
235    pub const fn end(self) -> Position {
236        self.end
237    }
238}
239
240impl fmt::Display for Span {
241    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
242        let _f = f;
243
244        match (self.start(), self.end()) {
245            (Position::NONE, Position::NONE) => write!(_f, "{:?}", Position::NONE),
246            (Position::NONE, end) => write!(_f, "..{end:?}"),
247            (start, Position::NONE) => write!(_f, "{start:?}"),
248            (start, end) if start.line() != end.line() => {
249                write!(_f, "{start:?}-{end:?}")
250            }
251            (start, end) => write!(
252                _f,
253                "{}:{}-{}",
254                start.line().unwrap(),
255                start.position().unwrap_or(0),
256                end.position().unwrap_or(0)
257            ),
258        }
259    }
260}
261
262impl fmt::Debug for Span {
263    #[cold]
264    #[inline(never)]
265    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
266        fmt::Display::fmt(self, f)
267    }
268}