1use std::borrow::Cow;
2
3#[derive(Clone, Debug, PartialEq, Eq)]
5pub enum Position {
6 LineNumberReference {
9 start: usize,
10 end: usize,
11 line_number: usize,
12 },
13 LineNumber {
15 content: Cow<'static, str>,
16 line_number: usize,
17 },
18 Any { content: Cow<'static, str> },
21}
22
23impl Position {
24 #[inline]
26 pub fn line_number(content: impl Into<Cow<'static, str>>, line_number: usize) -> Position {
27 Self::LineNumber {
28 content: content.into(),
29 line_number,
30 }
31 }
32}
33
34#[derive(Clone, Debug, PartialEq, Eq)]
36pub enum TriviaKind {
37 Comment,
39 Whitespace,
41}
42
43impl TriviaKind {
44 pub fn at(self, start: usize, end: usize, line_number: usize) -> Trivia {
46 Trivia {
47 position: Position::LineNumberReference {
48 start,
49 end,
50 line_number,
51 },
52 kind: self,
53 }
54 }
55
56 pub fn with_content<IntoCowStr: Into<Cow<'static, str>>>(self, content: IntoCowStr) -> Trivia {
58 Trivia {
59 position: Position::Any {
60 content: content.into(),
61 },
62 kind: self,
63 }
64 }
65}
66
67#[derive(Clone, Debug, PartialEq, Eq)]
69pub struct Trivia {
70 position: Position,
71 kind: TriviaKind,
72}
73
74impl Trivia {
75 pub fn read<'a: 'b, 'b>(&'a self, code: &'b str) -> &'b str {
80 match &self.position {
81 Position::LineNumberReference { start, end, .. } => {
82 code.get(*start..*end).unwrap_or_else(|| {
83 panic!("unable to extract code from position: {} - {}", start, end);
84 })
85 }
86 Position::LineNumber { content, .. } | Position::Any { content } => content,
87 }
88 }
89
90 pub fn try_read(&self) -> Option<&str> {
94 match &self.position {
95 Position::LineNumberReference { .. } => None,
96 Position::LineNumber { content, .. } | Position::Any { content } => Some(content),
97 }
98 }
99
100 pub fn kind(&self) -> TriviaKind {
102 self.kind.clone()
103 }
104
105 pub fn get_line_number(&self) -> Option<usize> {
107 match &self.position {
108 Position::LineNumber { line_number, .. }
109 | Position::LineNumberReference { line_number, .. } => Some(*line_number),
110 Position::Any { .. } => None,
111 }
112 }
113}
114
115#[derive(Clone, Debug, PartialEq, Eq)]
117pub struct Token {
118 position: Position,
119 leading_trivia: Vec<Trivia>,
120 trailing_trivia: Vec<Trivia>,
121}
122
123impl Token {
124 pub fn new_with_line(start: usize, end: usize, line_number: usize) -> Self {
127 Self {
128 position: Position::LineNumberReference {
129 start,
130 end,
131 line_number,
132 },
133 leading_trivia: Vec::new(),
134 trailing_trivia: Vec::new(),
135 }
136 }
137
138 pub fn from_content<IntoCowStr: Into<Cow<'static, str>>>(content: IntoCowStr) -> Self {
140 Self {
141 position: Position::Any {
142 content: content.into(),
143 },
144 leading_trivia: Vec::new(),
145 trailing_trivia: Vec::new(),
146 }
147 }
148
149 pub fn from_position(position: Position) -> Self {
151 Self {
152 position,
153 leading_trivia: Vec::new(),
154 trailing_trivia: Vec::new(),
155 }
156 }
157
158 pub fn with_leading_trivia(mut self, trivia: Trivia) -> Self {
160 self.leading_trivia.push(trivia);
161 self
162 }
163
164 pub fn with_trailing_trivia(mut self, trivia: Trivia) -> Self {
166 self.trailing_trivia.push(trivia);
167 self
168 }
169
170 #[inline]
172 pub fn has_trivia(&self) -> bool {
173 !self.leading_trivia.is_empty() || !self.trailing_trivia.is_empty()
174 }
175
176 #[inline]
178 pub fn push_leading_trivia(&mut self, trivia: Trivia) {
179 self.leading_trivia.push(trivia);
180 }
181
182 pub fn insert_leading_trivia(&mut self, index: usize, trivia: Trivia) {
184 if index > self.leading_trivia.len() {
185 self.leading_trivia.push(trivia);
186 } else {
187 self.leading_trivia.insert(index, trivia);
188 }
189 }
190
191 #[inline]
193 pub fn push_trailing_trivia(&mut self, trivia: Trivia) {
194 self.trailing_trivia.push(trivia);
195 }
196
197 #[inline]
199 pub fn iter_leading_trivia(&self) -> impl Iterator<Item = &Trivia> {
200 self.leading_trivia.iter()
201 }
202
203 #[inline]
205 pub fn drain_leading_trivia(&mut self) -> impl Iterator<Item = Trivia> + '_ {
206 self.leading_trivia.drain(..)
207 }
208
209 #[inline]
211 pub fn iter_trailing_trivia(&self) -> impl Iterator<Item = &Trivia> {
212 self.trailing_trivia.iter()
213 }
214
215 #[inline]
217 pub fn drain_trailing_trivia(&mut self) -> impl Iterator<Item = Trivia> + '_ {
218 self.trailing_trivia.drain(..)
219 }
220
221 pub fn read<'a: 'b, 'b>(&'a self, code: &'b str) -> &'b str {
226 match &self.position {
227 Position::LineNumberReference { start, end, .. } => code
228 .get(*start..*end)
229 .expect("unable to extract code from position"),
230 Position::LineNumber { content, .. } | Position::Any { content } => content,
231 }
232 }
233
234 pub fn get_line_number(&self) -> Option<usize> {
236 match &self.position {
237 Position::LineNumber { line_number, .. }
238 | Position::LineNumberReference { line_number, .. } => Some(*line_number),
239 Position::Any { .. } => None,
240 }
241 }
242
243 pub fn replace_with_content<IntoCowStr: Into<Cow<'static, str>>>(
245 &mut self,
246 content: IntoCowStr,
247 ) {
248 self.position = match &self.position {
249 Position::LineNumber { line_number, .. }
250 | Position::LineNumberReference { line_number, .. } => Position::LineNumber {
251 line_number: *line_number,
252 content: content.into(),
253 },
254
255 Position::Any { .. } => Position::Any {
256 content: content.into(),
257 },
258 };
259 }
260
261 pub fn clear_comments(&mut self) {
263 self.leading_trivia
264 .retain(|trivia| trivia.kind() != TriviaKind::Comment);
265 self.trailing_trivia
266 .retain(|trivia| trivia.kind() != TriviaKind::Comment);
267 }
268
269 pub fn clear_whitespaces(&mut self) {
271 self.leading_trivia
272 .retain(|trivia| trivia.kind() != TriviaKind::Whitespace);
273 self.trailing_trivia
274 .retain(|trivia| trivia.kind() != TriviaKind::Whitespace);
275 }
276
277 pub(crate) fn filter_comments(&mut self, filter: impl Fn(&Trivia) -> bool) {
278 self.leading_trivia
279 .retain(|trivia| trivia.kind() != TriviaKind::Comment || filter(trivia));
280 self.trailing_trivia
281 .retain(|trivia| trivia.kind() != TriviaKind::Comment || filter(trivia));
282 }
283
284 pub(crate) fn replace_referenced_tokens(&mut self, code: &str) {
285 if let Position::LineNumberReference {
286 start,
287 end,
288 line_number,
289 } = self.position
290 {
291 self.position = Position::LineNumber {
292 line_number,
293 content: code
294 .get(start..end)
295 .expect("unable to extract code from position")
296 .to_owned()
297 .into(),
298 }
299 };
300 for trivia in self
301 .leading_trivia
302 .iter_mut()
303 .chain(self.trailing_trivia.iter_mut())
304 {
305 if let Position::LineNumberReference {
306 start,
307 end,
308 line_number,
309 } = trivia.position
310 {
311 trivia.position = Position::LineNumber {
312 line_number,
313 content: code
314 .get(start..end)
315 .expect("unable to extract code from position")
316 .to_owned()
317 .into(),
318 }
319 };
320 }
321 }
322
323 pub(crate) fn shift_token_line(&mut self, amount: isize) {
324 match &mut self.position {
325 Position::LineNumberReference { line_number, .. }
326 | Position::LineNumber { line_number, .. } => {
327 *line_number = line_number.saturating_add_signed(amount);
328 }
329 Position::Any { .. } => {}
330 }
331 }
332}
333
334#[cfg(test)]
335mod test {
336 use super::*;
337
338 #[test]
339 fn read_line_number_reference_token() {
340 let code = "return true";
341 let token = Token::new_with_line(7, 11, 1);
342
343 assert_eq!("true", token.read(code));
344 }
345
346 #[test]
347 fn read_any_position_token() {
348 let token = Token::from_content("true");
349
350 assert_eq!("true", token.read(""));
351 }
352}