arithmetic_parser/
spans.rs

1//! Types related to spanning parsed code.
2
3use core::ops;
4
5use nom::Input;
6
7use crate::{
8    alloc::{format, String},
9    Error,
10};
11
12/// Code span.
13pub type InputSpan<'a> = nom_locate::LocatedSpan<&'a str, ()>;
14/// Parsing outcome generalized by the type returned on success.
15pub type NomResult<'a, T> = nom::IResult<InputSpan<'a>, T, Error>;
16
17/// Code span together with information related to where it is located in the code.
18///
19/// This type is similar to one from the [`nom_locate`] crate, but it has slightly different
20/// functionality. In particular, this type provides no method to access other parts of the code
21/// (which is performed in `nom_locate`'s `LocatedSpan::get_column()` among other methods).
22/// As such, this allows to safely replace [span info](#method.fragment) without worrying
23/// about undefined behavior.
24///
25/// [`nom_locate`]: https://crates.io/crates/nom_locate
26#[derive(Debug, Clone, Copy)]
27pub struct LocatedSpan<Span, T = ()> {
28    offset: usize,
29    line: u32,
30    column: usize,
31    fragment: Span,
32
33    /// Extra information that can be embedded by the user.
34    pub extra: T,
35}
36
37impl<Span: PartialEq, T> PartialEq for LocatedSpan<Span, T> {
38    fn eq(&self, other: &Self) -> bool {
39        self.line == other.line && self.offset == other.offset && self.fragment == other.fragment
40    }
41}
42
43impl<Span, T> LocatedSpan<Span, T> {
44    /// The offset represents the position of the fragment relatively to the input of the parser.
45    /// It starts at offset 0.
46    pub fn location_offset(&self) -> usize {
47        self.offset
48    }
49
50    /// The line number of the fragment relatively to the input of the parser. It starts at line 1.
51    pub fn location_line(&self) -> u32 {
52        self.line
53    }
54
55    /// The column of the fragment start.
56    pub fn get_column(&self) -> usize {
57        self.column
58    }
59
60    /// The fragment that is spanned. The fragment represents a part of the input of the parser.
61    pub fn fragment(&self) -> &Span {
62        &self.fragment
63    }
64
65    /// Maps the `extra` field of this span using the provided closure.
66    pub fn map_extra<U>(self, map_fn: impl FnOnce(T) -> U) -> LocatedSpan<Span, U> {
67        LocatedSpan {
68            offset: self.offset,
69            line: self.line,
70            column: self.column,
71            fragment: self.fragment,
72            extra: map_fn(self.extra),
73        }
74    }
75
76    /// Maps the fragment field of this span using the provided closure.
77    pub fn map_fragment<U>(self, map_fn: impl FnOnce(Span) -> U) -> LocatedSpan<U, T> {
78        LocatedSpan {
79            offset: self.offset,
80            line: self.line,
81            column: self.column,
82            fragment: map_fn(self.fragment),
83            extra: self.extra,
84        }
85    }
86}
87
88impl<Span: Copy, T> LocatedSpan<Span, T> {
89    /// Returns a copy of this span with borrowed `extra` field.
90    pub fn as_ref(&self) -> LocatedSpan<Span, &T> {
91        LocatedSpan {
92            offset: self.offset,
93            line: self.line,
94            column: self.column,
95            fragment: self.fragment,
96            extra: &self.extra,
97        }
98    }
99
100    /// Copies this span with the provided `extra` field.
101    pub fn copy_with_extra<U>(&self, value: U) -> LocatedSpan<Span, U> {
102        LocatedSpan {
103            offset: self.offset,
104            line: self.line,
105            column: self.column,
106            fragment: self.fragment,
107            extra: value,
108        }
109    }
110
111    /// Removes `extra` field from this span.
112    pub fn with_no_extra(&self) -> LocatedSpan<Span> {
113        self.copy_with_extra(())
114    }
115}
116
117#[allow(clippy::mismatching_type_param_order)] // weird false positive
118impl<'a, T> From<nom_locate::LocatedSpan<&'a str, T>> for LocatedSpan<&'a str, T> {
119    fn from(value: nom_locate::LocatedSpan<&'a str, T>) -> Self {
120        Self {
121            offset: value.location_offset(),
122            line: value.location_line(),
123            column: value.get_column(),
124            fragment: *value.fragment(),
125            extra: value.extra,
126        }
127    }
128}
129
130/// Value with an associated code span.
131pub type Spanned<'a, T = ()> = LocatedSpan<&'a str, T>;
132
133impl<'a, T> Spanned<'a, T> {
134    pub(crate) fn new(span: InputSpan<'a>, extra: T) -> Self {
135        Self {
136            offset: span.location_offset(),
137            line: span.location_line(),
138            column: span.get_column(),
139            fragment: *span.fragment(),
140            extra,
141        }
142    }
143}
144
145impl<'a> Spanned<'a> {
146    /// Creates a span from a `range` in the provided `code`. This is mostly useful for testing.
147    pub fn from_str<R>(code: &'a str, range: R) -> Self
148    where
149        InputSpan<'a>: Input,
150        R: ops::RangeBounds<usize>,
151    {
152        let input = InputSpan::new(code);
153        let start = match range.start_bound() {
154            ops::Bound::Unbounded => 0,
155            ops::Bound::Included(&i) => i,
156            ops::Bound::Excluded(&i) => i + 1,
157        };
158        let mut input = input.take_from(start);
159
160        let len = match range.end_bound() {
161            ops::Bound::Unbounded => None,
162            ops::Bound::Included(&i) => Some((i + 1).saturating_sub(start)),
163            ops::Bound::Excluded(&i) => Some(i.saturating_sub(start)),
164        };
165        if let Some(len) = len {
166            input = input.take(len);
167        }
168
169        Self::new(input, ())
170    }
171}
172
173/// Value with an associated code location. Unlike [`Spanned`], `Location` does not retain a reference
174/// to the original code span, just its start position and length.
175pub type Location<T = ()> = LocatedSpan<usize, T>;
176
177impl Location {
178    /// Creates a location from a `range` in the provided `code`. This is mostly useful for testing.
179    pub fn from_str<'a, R>(code: &'a str, range: R) -> Self
180    where
181        InputSpan<'a>: Input,
182        R: ops::RangeBounds<usize>,
183    {
184        Spanned::from_str(code, range).into()
185    }
186}
187
188impl<T> Location<T> {
189    /// Returns a string representation of this location in the form `{default_name} at {line}:{column}`.
190    pub fn to_string(&self, default_name: &str) -> String {
191        format!("{default_name} at {}:{}", self.line, self.column)
192    }
193
194    /// Returns this location in the provided `code`. It is caller's responsibility to ensure that this
195    /// is called with the original `code` that produced this location.
196    pub fn span<'a>(&self, code: &'a str) -> &'a str {
197        &code[self.offset..(self.offset + self.fragment)]
198    }
199}
200
201impl<T> From<Spanned<'_, T>> for Location<T> {
202    fn from(value: Spanned<'_, T>) -> Self {
203        value.map_fragment(str::len)
204    }
205}
206
207/// Wrapper around parsers allowing to capture both their output and the relevant span.
208pub fn with_span<'a, O, F>(
209    mut parser: F,
210) -> impl nom::Parser<InputSpan<'a>, Output = Spanned<'a, O>, Error = F::Error>
211where
212    F: nom::Parser<InputSpan<'a>, Output = O>,
213{
214    move |input: InputSpan<'a>| {
215        parser.parse(input).map(|(rest, output)| {
216            let len = rest.location_offset() - input.location_offset();
217            let spanned = Spanned {
218                offset: input.location_offset(),
219                line: input.location_line(),
220                column: input.get_column(),
221                fragment: &input.fragment()[..len],
222                extra: output,
223            };
224            (rest, spanned)
225        })
226    }
227}
228
229pub(crate) fn unite_spans<'a, T, U>(
230    input: InputSpan<'a>,
231    start: &Spanned<'_, T>,
232    end: &Spanned<'_, U>,
233) -> Spanned<'a> {
234    debug_assert!(input.location_offset() <= start.location_offset());
235    debug_assert!(start.location_offset() <= end.location_offset());
236    debug_assert!(
237        input.location_offset() + input.fragment().len()
238            >= end.location_offset() + end.fragment().len()
239    );
240
241    let start_idx = start.location_offset() - input.location_offset();
242    let end_idx = end.location_offset() + end.fragment().len() - input.location_offset();
243    Spanned {
244        offset: start.offset,
245        line: start.line,
246        column: start.column,
247        fragment: &input.fragment()[start_idx..end_idx],
248        extra: (),
249    }
250}