jpar/reader/
span.rs

1use memchr::{memchr, memrchr};
2
3use crate::reader::Cursor;
4
5/// A Span is a set of meta information about the location of a substring.
6#[derive(Debug, Clone, Eq, PartialEq)]
7pub struct Span<'a> {
8    content: &'a str,
9    start_cursor: Cursor,
10    end_cursor: Cursor,
11}
12
13impl<'a> Span<'a> {
14    // CONSTRUCTORS -----------------------------------------------------------
15
16    /// Builds a new `Span` with the specified data.
17    pub(in crate::reader) fn new(
18        content: &'a str,
19        start_cursor: Cursor,
20        end_cursor: Cursor,
21    ) -> Span {
22        Span {
23            content,
24            start_cursor,
25            end_cursor,
26        }
27    }
28
29    // GETTERS ----------------------------------------------------------------
30
31    /// The whole content the `Span` belongs to.
32    pub fn whole_content(&self) -> &'a str {
33        self.content
34    }
35
36    /// The content of the `Span`.
37    pub fn content(&self) -> &'a str {
38        &self.content[self.start_cursor.byte_offset()..self.end_cursor.byte_offset()]
39    }
40
41    /// The content before the `Span`.
42    pub fn content_before(&self) -> &'a str {
43        &self.content[..self.start_cursor.byte_offset()]
44    }
45
46    /// The content after the `Span`.
47    pub fn content_after(&self) -> &'a str {
48        &self.content[self.end_cursor.byte_offset()..]
49    }
50
51    /// The start position of the `Span` in bytes.
52    pub fn start_cursor(&self) -> &Cursor {
53        &self.start_cursor
54    }
55
56    /// The end position of the `Span` in bytes.
57    pub fn end_cursor(&self) -> &Cursor {
58        &self.end_cursor
59    }
60
61    /// The length of the `Span` in bytes.
62    pub fn len(&self) -> usize {
63        self.end_cursor.byte_offset() - self.start_cursor.byte_offset()
64    }
65
66    /// Whether the span is empty or not.
67    pub fn is_empty(&self) -> bool {
68        self.len() == 0
69    }
70
71    /// The length of the `Span` in characters.
72    pub fn char_length(&self) -> usize {
73        self.end_cursor.char_offset() - self.start_cursor.char_offset()
74    }
75
76    /// Returns the line(s) in which the `Span` is contained.
77    /// If it is composed of more than one line, the result will be all the lines.
78    ///
79    /// # Example
80    ///
81    /// ```
82    /// # use jpar::Reader;
83    /// let mut reader = Reader::new("This\nis\nthe\nfragment");
84    ///
85    /// // ... prepare the span to contain: "his\nis\nt" ...
86    /// # reader.read();
87    /// # let from_cursor = reader.save_cursor();
88    /// # reader.read_while(|i, c| i < "his\nis\nt".len());
89    /// # let to_cursor = reader.save_cursor();
90    /// let span = reader.substring(&from_cursor, &to_cursor);
91    ///
92    /// // Get its lines.
93    /// assert_eq!(span.lines(), "This\nis\nthe");
94    /// ```
95    pub fn lines(&self) -> &'a str {
96        let start_index = match memrchr(b'\n', self.content_before().as_bytes()) {
97            Some(v) => v + 1,
98            None => 0,
99        };
100
101        let end_index = match memchr(b'\n', self.content_after().as_bytes()) {
102            Some(v) => v + self.end_cursor.byte_offset(),
103            None => self.content.len(),
104        };
105
106        &self.content[start_index..end_index]
107    }
108}
109
110// ----------------------------------------------------------------------------
111// ----------------------------------------------------------------------------
112// ----------------------------------------------------------------------------
113
114#[cfg(test)]
115mod tests {
116    use super::*;
117
118    #[test]
119    fn test_lines_single_line() {
120        let text = "This\nis\nthe\ntest";
121        let span = Span::new(
122            text,
123            Cursor::new(1, 0, 0, 0), // Only offset matters.
124            Cursor::new(1, 0, 0, 0), // Only offset matters.
125        );
126
127        assert_eq!(span.lines(), "This", "The lines is incorrect");
128
129        // Check at \n
130        let text = "This\nis\nthe\ntest";
131        let span = Span::new(
132            text,
133            Cursor::new(4, 0, 0, 0), // Only offset matters.
134            Cursor::new(4, 0, 0, 0), // Only offset matters.
135        );
136
137        assert_eq!(span.lines(), "This", "The lines is incorrect");
138
139        // Check next of \n
140        let text = "This\nis\nthe\ntest";
141        let span = Span::new(
142            text,
143            Cursor::new(5, 0, 0, 0), // Only offset matters.
144            Cursor::new(5, 0, 0, 0), // Only offset matters.
145        );
146
147        assert_eq!(span.lines(), "is", "The lines is incorrect");
148    }
149
150    #[test]
151    fn test_lines_multiline() {
152        let text = "This\nis\nthe\ntest";
153        let span = Span::new(
154            text,
155            Cursor::new(5, 0, 0, 0),  // Only offset matters.
156            Cursor::new(08, 0, 0, 0), // Only offset matters.
157        );
158
159        assert_eq!(span.lines(), "is\nthe", "The lines is incorrect");
160    }
161}