fuel_pest/
span.rs

1// pest. The Elegant Parser
2// Copyright (c) 2018 DragoČ™ Tiselice
3//
4// Licensed under the Apache License, Version 2.0
5// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0> or the MIT
6// license <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7// option. All files in the project carrying such notice may not be copied,
8// modified, or distributed except according to those terms.
9
10use std::fmt;
11use std::hash::{Hash, Hasher};
12use std::str;
13use std::sync::Arc;
14
15use position;
16
17/// A span over a `&str`. It is created from either [two `Position`s] or from a [`Pair`].
18///
19/// [two `Position`s]: struct.Position.html#method.span
20/// [`Pair`]: ../iterators/struct.Pair.html#method.span
21#[derive(Clone)]
22pub struct Span {
23    input: Arc<str>,
24    /// # Safety
25    ///
26    /// Must be a valid character boundary index into `input`.
27    start: usize,
28    /// # Safety
29    ///
30    /// Must be a valid character boundary index into `input`.
31    end: usize,
32}
33
34impl Span {
35    /// Get the original input that this `Span` refers to without being indexed from `start` to
36    /// `end`.
37    pub fn input(&self) -> &Arc<str> {
38        &self.input
39    }
40    /// Create a new `Span` without checking invariants. (Checked with `debug_assertions`.)
41    ///
42    /// # Safety
43    ///
44    /// `input[start..end]` must be a valid subslice; that is, said indexing should not panic.
45    pub unsafe fn new_unchecked(input: Arc<str>, start: usize, end: usize) -> Span {
46        debug_assert!(input.get(start..end).is_some());
47        Span { input, start, end }
48    }
49
50    /// Attempts to create a new span. Will return `None` if `input[start..end]` is an invalid index
51    /// into `input`.
52    ///
53    /// # Examples
54    ///
55    /// ```
56    /// # use pest::Span;
57    /// # use std::sync::Arc;
58    /// let input: Arc<str> = Arc::from("Hello!");
59    /// assert_eq!(None, Span::new(input.clone(), 100, 0));
60    /// assert!(Span::new(input.clone(), 0, input.len()).is_some());
61    /// ```
62    #[allow(clippy::new_ret_no_self)]
63    pub fn new(input: Arc<str>, start: usize, end: usize) -> Option<Span> {
64        if input.get(start..end).is_some() {
65            Some(Span { input, start, end })
66        } else {
67            None
68        }
69    }
70
71    /// Returns the `Span`'s start byte position as a `usize`.
72    ///
73    /// # Examples
74    ///
75    /// ```
76    /// # use pest::Position;
77    /// # use std::sync::Arc;
78    /// let input: Arc<str> = Arc::from("ab");
79    /// let start = Position::from_start(input);
80    /// let end = start.clone();
81    /// let span = start.span(&end);
82    ///
83    /// assert_eq!(span.start(), 0);
84    /// ```
85    #[inline]
86    pub fn start(&self) -> usize {
87        self.start
88    }
89
90    /// Returns the `Span`'s end byte position as a `usize`.
91    ///
92    /// # Examples
93    ///
94    /// ```
95    /// # use pest::Position;
96    /// # use std::sync::Arc;
97    /// let input: Arc<str> = Arc::from("ab");
98    /// let start = Position::from_start(input);
99    /// let end = start.clone();
100    /// let span = start.span(&end);
101    ///
102    /// assert_eq!(span.end(), 0);
103    /// ```
104    #[inline]
105    pub fn end(&self) -> usize {
106        self.end
107    }
108
109    /// Returns the `Span`'s start `Position`.
110    ///
111    /// # Examples
112    ///
113    /// ```
114    /// # use pest::Position;
115    /// # use std::sync::Arc;
116    /// let input: Arc<str> = Arc::from("ab");
117    /// let start = Position::from_start(input);
118    /// let end = start.clone();
119    /// let span = start.clone().span(&end);
120    ///
121    /// assert_eq!(span.start_pos(), start);
122    /// ```
123    #[inline]
124    pub fn start_pos(&self) -> position::Position {
125        // Span's start position is always a UTF-8 border.
126        unsafe { position::Position::new_unchecked(self.input.clone(), self.start) }
127    }
128
129    /// Returns the `Span`'s end `Position`.
130    ///
131    /// # Examples
132    ///
133    /// ```
134    /// # use pest::Position;
135    /// # use std::sync::Arc;
136    /// let input: Arc<str> = Arc::from("ab");
137    /// let start = Position::from_start(input);
138    /// let end = start.clone();
139    /// let span = start.span(&end);
140    ///
141    /// assert_eq!(span.end_pos(), end);
142    /// ```
143    #[inline]
144    pub fn end_pos(&self) -> position::Position {
145        // Span's end position is always a UTF-8 border.
146        unsafe { position::Position::new_unchecked(self.input.clone(), self.end) }
147    }
148
149    /// Splits the `Span` into a pair of `Position`s.
150    ///
151    /// # Examples
152    ///
153    /// ```
154    /// # use pest::Position;
155    /// # use std::sync::Arc;
156    /// let input: Arc<str> = Arc::from("ab");
157    /// let start = Position::from_start(input);
158    /// let end = start.clone();
159    /// let span = start.clone().span(&end);
160    ///
161    /// assert_eq!(span.split(), (start, end));
162    /// ```
163    #[inline]
164    pub fn split(self) -> (position::Position, position::Position) {
165        // Span's start and end positions are always a UTF-8 borders.
166        let pos1 = unsafe { position::Position::new_unchecked(self.input.clone(), self.start) };
167        let pos2 = unsafe { position::Position::new_unchecked(self.input, self.end) };
168
169        (pos1, pos2)
170    }
171
172    /// Captures a slice from the `&str` defined by the `Span`.
173    ///
174    /// # Examples
175    ///
176    /// ```
177    /// # use pest;
178    /// # use std::sync::Arc;
179    /// # #[allow(non_camel_case_types)]
180    /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
181    /// enum Rule {}
182    ///
183    /// let input: Arc<str> = Arc::from("abc");
184    /// let mut state: Box<pest::ParserState<Rule>> = pest::ParserState::new(input).skip(1).unwrap();
185    /// let start_pos = state.position().clone();
186    /// state = state.match_string("b").unwrap();
187    /// let span = start_pos.span(&state.position().clone());
188    /// assert_eq!(span.as_str(), "b");
189    /// ```
190    #[inline]
191    pub fn as_str(&self) -> &str {
192        // Span's start and end positions are always a UTF-8 borders.
193        &self.input[self.start..self.end]
194    }
195
196    /// Iterates over all lines (partially) covered by this span.
197    ///
198    /// # Examples
199    ///
200    /// ```
201    /// # use pest;
202    /// # use std::sync::Arc;
203    /// # #[allow(non_camel_case_types)]
204    /// # #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
205    /// enum Rule {}
206    ///
207    /// let input: Arc<str> = Arc::from("a\nb\nc");
208    /// let mut state: Box<pest::ParserState<Rule>> = pest::ParserState::new(input).skip(2).unwrap();
209    /// let start_pos = state.position().clone();
210    /// state = state.match_string("b\nc").unwrap();
211    /// let span = start_pos.span(&state.position().clone());
212    /// assert_eq!(span.lines().collect::<Vec<_>>(), vec!["b\n", "c"]);
213    /// ```
214    #[inline]
215    pub fn lines(&self) -> Lines {
216        Lines {
217            span: self,
218            pos: self.start,
219        }
220    }
221}
222
223impl fmt::Debug for Span {
224    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
225        f.debug_struct("Span")
226            .field("str", &self.as_str())
227            .field("start", &self.start)
228            .field("end", &self.end)
229            .finish()
230    }
231}
232
233impl PartialEq for Span {
234    fn eq(&self, other: &Span) -> bool {
235        Arc::ptr_eq(&self.input, &other.input) && self.start == other.start && self.end == other.end
236    }
237}
238
239impl Eq for Span {}
240
241impl Hash for Span {
242    fn hash<H: Hasher>(&self, state: &mut H) {
243        Arc::as_ptr(&self.input).hash(state);
244        self.start.hash(state);
245        self.end.hash(state);
246    }
247}
248
249/// Line iterator for Spans, created by [`Span::lines()`].
250///
251/// Iterates all lines that are at least partially covered by the span.
252///
253/// [`Span::lines()`]: struct.Span.html#method.lines
254pub struct Lines<'i> {
255    span: &'i Span,
256    pos: usize,
257}
258
259impl<'i> Iterator for Lines<'i> {
260    type Item = &'i str;
261    fn next(&mut self) -> Option<&'i str> {
262        if self.pos > self.span.end {
263            return None;
264        }
265        let pos = position::Position::new(self.span.input.clone(), self.pos)?;
266        if pos.at_end() {
267            return None;
268        }
269        let line_start = pos.find_line_start();
270        let line_end = pos.find_line_end();
271        let line = &self.span.input[line_start..line_end];
272        self.pos = line_end;
273        Some(line)
274    }
275}
276
277#[cfg(test)]
278mod tests {
279    use super::*;
280    use alloc::borrow::ToOwned;
281    use alloc::vec::Vec;
282
283    #[test]
284    fn split() {
285        let input = Arc::from("a");
286        let start = position::Position::from_start(input);
287        let mut end = start.clone();
288
289        assert!(end.skip(1));
290
291        let span = start.clone().span(&end.clone());
292
293        assert_eq!(span.split(), (start, end));
294    }
295
296    #[test]
297    fn lines_mid() {
298        let input = Arc::from("abc\ndef\nghi");
299        let span = Span::new(input, 1, 7).unwrap();
300        let lines: Vec<_> = span.lines().collect();
301        //println!("{:?}", lines);
302        assert_eq!(lines.len(), 2);
303        assert_eq!(lines[0], "abc\n".to_owned());
304        assert_eq!(lines[1], "def\n".to_owned());
305    }
306
307    #[test]
308    fn lines_eof() {
309        let input = Arc::from("abc\ndef\nghi");
310        let span = Span::new(input, 5, 11).unwrap();
311        assert!(span.end_pos().at_end());
312        let lines: Vec<_> = span.lines().collect();
313        //println!("{:?}", lines);
314        assert_eq!(lines.len(), 2);
315        assert_eq!(lines[0], "def\n".to_owned());
316        assert_eq!(lines[1], "ghi".to_owned());
317    }
318}