spo_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
80        // Advance up to maximum position
81        self.pos = self.pos.saturating_add(1);
82    }
83    /// Go backwards by one character position.
84    ///
85    /// # Panics
86    ///
87    /// Panics if already at beginning of a line - cannot rewind to a previous line.
88    #[inline]
89    pub(crate) fn rewind(&mut self) {
90        assert!(!self.is_none(), "cannot rewind Position::NONE");
91        assert!(self.pos > 0, "cannot rewind at position 0");
92        self.pos -= 1;
93    }
94    /// Advance to the next line.
95    #[inline]
96    pub(crate) fn new_line(&mut self) {
97        assert!(!self.is_none(), "cannot advance Position::NONE");
98
99        // Advance up to maximum position
100        if self.line < u16::MAX {
101            self.line += 1;
102            self.pos = 0;
103        }
104    }
105    /// Is this [`Position`] at the beginning of a line?
106    ///
107    /// Always returns `false` under `no_position`.
108    #[inline]
109    #[must_use]
110    pub const fn is_beginning_of_line(self) -> bool {
111        self.pos == 0 && !self.is_none()
112    }
113    /// Is there no [`Position`]?
114    ///
115    /// Always returns `true` under `no_position`.
116    #[inline]
117    #[must_use]
118    pub const fn is_none(self) -> bool {
119        self.line == 0 && self.pos == 0
120    }
121    /// Returns an fallback [`Position`] if it is [`NONE`][Position::NONE]?
122    ///
123    /// Always returns the fallback under `no_position`.
124    #[inline]
125    #[must_use]
126    pub const fn or_else(self, pos: Self) -> Self {
127        if self.is_none() {
128            pos
129        } else {
130            self
131        }
132    }
133}
134
135impl Default for Position {
136    #[inline(always)]
137    #[must_use]
138    fn default() -> Self {
139        Self::START
140    }
141}
142
143impl fmt::Display for Position {
144    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
145        if self.is_none() {
146            write!(f, "none")
147        } else {
148            write!(f, "line {}, position {}", self.line, self.pos)
149        }
150    }
151}
152
153impl fmt::Debug for Position {
154    #[cold]
155    #[inline(never)]
156    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
157        if self.is_none() {
158            f.write_str("none")
159        } else if self.is_beginning_of_line() {
160            write!(f, "{}", self.line)
161        } else {
162            write!(f, "{}:{}", self.line, self.pos)
163        }
164    }
165}
166
167impl Add for Position {
168    type Output = Self;
169
170    fn add(self, rhs: Self) -> Self::Output {
171        if rhs.is_none() {
172            self
173        } else {
174            Self {
175                line: self.line + rhs.line - 1,
176                pos: if rhs.is_beginning_of_line() {
177                    self.pos
178                } else {
179                    self.pos + rhs.pos - 1
180                },
181            }
182        }
183    }
184}
185
186impl AddAssign for Position {
187    fn add_assign(&mut self, rhs: Self) {
188        *self = *self + rhs;
189    }
190}
191
192/// _(internals)_ A span consisting of a starting and an ending [positions][Position].
193/// Exported under the `internals` feature only.
194#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy)]
195pub struct Span {
196    /// Starting [position][Position].
197    start: Position,
198    /// Ending [position][Position].
199    end: Position,
200}
201
202impl Default for Span {
203    #[inline(always)]
204    #[must_use]
205    fn default() -> Self {
206        Self::NONE
207    }
208}
209
210impl Span {
211    /// Empty [`Span`].
212    pub const NONE: Self = Self::new(Position::NONE, Position::NONE);
213
214    /// Create a new [`Span`].
215    #[inline(always)]
216    #[must_use]
217    pub const fn new(start: Position, end: Position) -> Self {
218        Self { start, end }
219    }
220    /// Is this [`Span`] non-existent?
221    ///
222    /// Always returns `true` under `no_position`.
223    #[inline(always)]
224    #[must_use]
225    pub const fn is_none(self) -> bool {
226        self.start.is_none() && self.end.is_none()
227    }
228    /// Get the [`Span`]'s starting [position][Position].
229    ///
230    /// Always returns [`Position::NONE`] under `no_position`.
231    #[inline(always)]
232    #[must_use]
233    pub const fn start(self) -> Position {
234        self.start
235    }
236    /// Get the [`Span`]'s ending [position][Position].
237    ///
238    /// Always returns [`Position::NONE`] under `no_position`.
239    #[inline(always)]
240    #[must_use]
241    pub const fn end(self) -> Position {
242        self.end
243    }
244}
245
246impl fmt::Display for Span {
247    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
248        let _f = f;
249
250        match (self.start(), self.end()) {
251            (Position::NONE, Position::NONE) => write!(_f, "{:?}", Position::NONE),
252            (Position::NONE, end) => write!(_f, "..{end:?}"),
253            (start, Position::NONE) => write!(_f, "{start:?}"),
254            (start, end) if start.line() != end.line() => {
255                write!(_f, "{start:?}-{end:?}")
256            }
257            (start, end) => write!(
258                _f,
259                "{}:{}-{}",
260                start.line().unwrap(),
261                start.position().unwrap_or(0),
262                end.position().unwrap_or(0)
263            ),
264        }
265    }
266}
267
268impl fmt::Debug for Span {
269    #[cold]
270    #[inline(never)]
271    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
272        fmt::Display::fmt(self, f)
273    }
274}