darklua_core/nodes/
token.rs

1use std::borrow::Cow;
2
3/// Represents a position in the source code.
4#[derive(Clone, Debug, PartialEq, Eq)]
5pub enum Position {
6    /// A position that references a specific range in the source code
7    /// with line number information.
8    LineNumberReference {
9        start: usize,
10        end: usize,
11        line_number: usize,
12    },
13    /// A position that contains content and line number information.
14    LineNumber {
15        content: Cow<'static, str>,
16        line_number: usize,
17    },
18    /// A position that only contains content without any line number
19    /// information.
20    Any { content: Cow<'static, str> },
21}
22
23impl Position {
24    /// Creates a new position with line number information and content.
25    #[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/// An enum to represent source code text.
35#[derive(Clone, Debug, PartialEq, Eq)]
36pub enum TriviaKind {
37    /// A comment.
38    Comment,
39    /// Whitespace characters.
40    Whitespace,
41}
42
43impl TriviaKind {
44    /// Creates a new trivia with line number reference information.
45    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    /// Creates a new trivia with content.
57    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/// Represents a piece of trivia (whitespace or comments) in the source code.
68#[derive(Clone, Debug, PartialEq, Eq)]
69pub struct Trivia {
70    position: Position,
71    kind: TriviaKind,
72}
73
74impl Trivia {
75    /// Reads the content of the trivia from the source code.
76    ///
77    /// # Panics
78    /// Panics if the position is a line number reference and the range is invalid.
79    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    /// Attempts to read the content of the trivia without requiring source code.
91    ///
92    /// Returns `None` if the position is a line number reference, as it requires source code to read.
93    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    /// Returns the kind of trivia.
101    pub fn kind(&self) -> TriviaKind {
102        self.kind.clone()
103    }
104
105    /// Returns the line number of the trivia, if available.
106    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/// Represents a token in the source code with its position and associated comments or whitespaces.
116#[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    /// Creates a token where the position refers to the original code where
125    /// the token was parsed with the line number where it starts.
126    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    /// Creates a new token that is not constrained to any existing position.
139    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    /// Creates a new token from a position.
150    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    /// Adds leading trivia to the token and returns the updated token.
159    pub fn with_leading_trivia(mut self, trivia: Trivia) -> Self {
160        self.leading_trivia.push(trivia);
161        self
162    }
163
164    /// Adds trailing trivia to the token and returns the updated token.
165    pub fn with_trailing_trivia(mut self, trivia: Trivia) -> Self {
166        self.trailing_trivia.push(trivia);
167        self
168    }
169
170    /// Returns whether the token has any trivia (leading or trailing).
171    #[inline]
172    pub fn has_trivia(&self) -> bool {
173        !self.leading_trivia.is_empty() || !self.trailing_trivia.is_empty()
174    }
175
176    /// Adds leading trivia to the token.
177    #[inline]
178    pub fn push_leading_trivia(&mut self, trivia: Trivia) {
179        self.leading_trivia.push(trivia);
180    }
181
182    /// Inserts leading trivia at the given index.
183    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    /// Adds trailing trivia to the token.
192    #[inline]
193    pub fn push_trailing_trivia(&mut self, trivia: Trivia) {
194        self.trailing_trivia.push(trivia);
195    }
196
197    /// Returns an iterator over the leading trivia.
198    #[inline]
199    pub fn iter_leading_trivia(&self) -> impl Iterator<Item = &Trivia> {
200        self.leading_trivia.iter()
201    }
202
203    /// Returns an iterator over the leading trivia and removes them from the token.
204    #[inline]
205    pub fn drain_leading_trivia(&mut self) -> impl Iterator<Item = Trivia> + '_ {
206        self.leading_trivia.drain(..)
207    }
208
209    /// Returns an iterator over the trailing trivia.
210    #[inline]
211    pub fn iter_trailing_trivia(&self) -> impl Iterator<Item = &Trivia> {
212        self.trailing_trivia.iter()
213    }
214
215    /// Returns an iterator over the trailing trivia and removes them from the token.
216    #[inline]
217    pub fn drain_trailing_trivia(&mut self) -> impl Iterator<Item = Trivia> + '_ {
218        self.trailing_trivia.drain(..)
219    }
220
221    /// Reads the content of the token from the source code.
222    ///
223    /// # Panics
224    /// Panics if the position is a line number reference and the range is invalid.
225    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    /// Returns the line number of the token, if available.
235    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    /// Replaces the token's content with new content while preserving line number information.
244    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    /// Clears all comments from the tokens in this node.
262    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    /// Clears all whitespaces information from the tokens in this node.
270    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}