pear/input/
text.rs

1pub use crate::input::{Input, Rewind, Show, ParserInfo};
2
3#[cfg(feature = "color")]
4use yansi::Paint;
5
6#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
7pub struct Span<'a> {
8    /// Start line/column/offset.
9    pub start: (usize, usize, usize),
10    /// End line/column/offset.
11    pub end: (usize, usize, usize),
12    /// Where the parser was pointing.
13    pub cursor: Option<char>,
14    /// Snippet between start and end.
15    pub snippet: Option<&'a str>,
16}
17
18const SNIPPET_LEN: usize = 30;
19
20impl<'a> Show for Span<'a> {
21    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22        let (a, b, _) = self.start;
23        let (c, d, _) = self.end;
24
25        if self.start == self.end {
26            write!(f, "{}:{}", a, b)?;
27        } else {
28            write!(f, "{}:{} to {}:{}", a, b, c, d)?;
29        }
30
31        let write_snippet = |f: &mut std::fmt::Formatter<'_>, snippet: &str| {
32            for c in snippet.escape_debug() { write!(f, "{}", c)?; }
33            Ok(())
34        };
35
36        if let Some(snippet) = self.snippet {
37            write!(f, " \"")?;
38            if snippet.len() > SNIPPET_LEN + 6 {
39                write_snippet(f, &snippet[..SNIPPET_LEN / 2])?;
40
41                #[cfg(feature = "color")]
42                write!(f, " {} ", "...".blue())?;
43
44                #[cfg(not(feature = "color"))]
45                write!(f, " ... ")?;
46
47                let end_start = snippet.len() - SNIPPET_LEN / 2;
48                write_snippet(f, &snippet[end_start..])?;
49            } else {
50                write_snippet(f, snippet)?;
51            }
52
53            if let Some(cursor) = self.cursor {
54                #[cfg(feature = "color")]
55                write!(f, "{}", cursor.escape_debug().blue())?;
56
57                #[cfg(not(feature = "color"))]
58                write!(f, "{}", cursor.escape_debug())?;
59            }
60
61            write!(f, "\"")?;
62        } else {
63            #[cfg(feature = "color")]
64            write!(f, " {}", "[EOF]".blue())?;
65
66            #[cfg(not(feature = "color"))]
67            write!(f, " [EOF]")?;
68        }
69
70        Ok(())
71    }
72}
73
74#[derive(Debug)]
75pub struct Text<'a> {
76    current: &'a str,
77    start: &'a str,
78}
79
80impl<'a> From<&'a str> for Text<'a> {
81    fn from(start: &'a str) -> Text<'a> {
82        Text { start, current: start }
83    }
84}
85
86impl Rewind for Text<'_> {
87    fn rewind_to(&mut self, marker: Self::Marker) {
88        self.current = &self.start[marker..];
89    }
90}
91
92impl<'a> Input for Text<'a> {
93    type Token = char;
94    type Slice = &'a str;
95    type Many = Self::Slice;
96
97    type Marker = usize;
98    type Context = Span<'a>;
99
100    /// Returns a copy of the current token, if there is one.
101    fn token(&mut self) -> Option<Self::Token> {
102        self.current.token()
103    }
104
105    /// Returns a copy of the current slice of size `n`, if there is one.
106    fn slice(&mut self, n: usize) -> Option<Self::Slice> {
107        self.current.slice(n)
108    }
109
110    /// Checks if the current token fulfills `cond`.
111    fn peek<F>(&mut self, cond: F) -> bool
112        where F: FnMut(&Self::Token) -> bool
113    {
114        self.current.peek(cond)
115    }
116
117    /// Checks if the current slice of size `n` (if any) fulfills `cond`.
118    fn peek_slice<F>(&mut self, n: usize, cond: F) -> bool
119        where F: FnMut(&Self::Slice) -> bool
120    {
121        self.current.peek_slice(n, cond)
122    }
123
124    /// Checks if the current token fulfills `cond`. If so, the token is
125    /// consumed and returned. Otherwise, returns `None`.
126    fn eat<F>(&mut self, cond: F) -> Option<Self::Token>
127        where F: FnMut(&Self::Token) -> bool
128    {
129        self.current.eat(cond)
130    }
131
132    /// Checks if the current slice of size `n` (if any) fulfills `cond`. If so,
133    /// the slice is consumed and returned. Otherwise, returns `None`.
134    fn eat_slice<F>(&mut self, n: usize, cond: F) -> Option<Self::Slice>
135        where F: FnMut(&Self::Slice) -> bool
136    {
137        self.current.eat_slice(n, cond)
138    }
139
140    /// Takes tokens while `cond` returns true, collecting them into a
141    /// `Self::Many` and returning it.
142    fn take<F>(&mut self, cond: F) -> Self::Many
143        where F: FnMut(&Self::Token) -> bool
144    {
145        self.current.take(cond)
146    }
147
148    /// Skips tokens while `cond` returns true. Returns the number of skipped
149    /// tokens.
150    fn skip<F>(&mut self, cond: F) -> usize
151        where F: FnMut(&Self::Token) -> bool
152    {
153        self.current.skip(cond)
154    }
155
156    /// Returns `true` if there are at least `n` tokens remaining.
157    fn has(&mut self, n: usize) -> bool {
158        self.current.has(n)
159    }
160
161    #[inline(always)]
162    fn mark(&mut self, _: &ParserInfo) -> Self::Marker {
163        self.start.len() - self.current.len()
164    }
165
166    fn context(&mut self, mark: Self::Marker) -> Self::Context {
167        let cursor = self.token();
168        let bytes_read = self.start.len() - self.current.len();
169        if bytes_read == 0 {
170            Span { start: (1, 1, 0), end: (1, 1, 0), snippet: None, cursor }
171        } else {
172            let start_offset = mark;
173            let end_offset = bytes_read;
174
175            let to_start_str = &self.start[..start_offset];
176            let (start_line, start_col) = line_col(to_start_str);
177            let start = (start_line, start_col, start_offset);
178
179            let to_current_str = &self.start[..bytes_read];
180            let (end_line, end_col) = line_col(to_current_str);
181            let end = (end_line, end_col, bytes_read);
182
183            let snippet = if end_offset <= self.start.len() {
184                Some(&self.start[start_offset..end_offset])
185            } else {
186                None
187            };
188
189            Span { start, end, cursor, snippet }
190        }
191    }
192}
193
194fn line_col(string: &str) -> (usize, usize) {
195    if string.is_empty() {
196        return (1, 1);
197    }
198
199    let (line_count, last_line) = string.lines().enumerate().last().unwrap();
200    if string.ends_with('\n') {
201        (line_count + 2, 1)
202    } else {
203        (line_count + 1, last_line.len() + 1)
204    }
205}