Skip to main content

scarf_syntax/
metadata.rs

1// =======================================================================
2// metadata.rs
3// =======================================================================
4//! Extra metadata attached to leaf nodes to encode a CST
5
6use crate::*;
7use core::ops::Range;
8
9/// The start and end bytes of a particular portion of a source file
10pub type ByteSpan = Range<usize>;
11
12/// A representation of a unique location in a source file
13///
14/// If the file was included from another file (using the `` `include ``
15/// directive), [`Span::included_from`] will reference the [`Span`]
16/// of the include directive
17///
18/// If the [`Span`] is part of a `` `define `` directive, each expanded
19/// text macro will have the original [`Span`] of the (now expanded) macro,
20/// with [`Span::expanded_from`] referencing the original token (in the
21/// `` `define `` directive) before expansion
22#[derive(Default, Debug, Clone, PartialEq, Eq)]
23pub struct Span<'a> {
24    pub file: &'a str,
25    pub bytes: ByteSpan,
26    pub expanded_from: Option<&'a Span<'a>>,
27    pub included_from: Option<&'a Span<'a>>,
28}
29
30/// A relationship between two [`Span`]s, ordering them relative to
31/// each other in declaration order
32#[derive(Debug, PartialEq)]
33pub enum SpanRelation {
34    Earlier,
35    Later,
36    Same,
37}
38
39impl<'a> Span<'a> {
40    fn include_indeces(&self) -> Vec<usize> {
41        match &self.included_from {
42            None => {
43                vec![self.bytes.start]
44            }
45            Some(inner_span) => {
46                let mut nested_byte_indeces = inner_span.include_indeces();
47                nested_byte_indeces.push(self.bytes.start);
48                nested_byte_indeces
49            }
50        }
51    }
52    fn indeces_to_compare(&self) -> Vec<Vec<usize>> {
53        let mut indeces = vec![self.include_indeces()];
54        match self.expanded_from {
55            Some(expanded_span) => {
56                indeces.extend(expanded_span.indeces_to_compare())
57            }
58            None => (),
59        };
60        indeces
61    }
62    /// Compare two [`Span`]s, returning the relationship of the first relative to the second
63    ///
64    /// ```rust
65    /// # use scarf_syntax::*;
66    /// let span1 = Span {
67    ///     file: "test",
68    ///     bytes: (0..2),
69    ///     expanded_from: None,
70    ///     included_from: None
71    /// };
72    /// let span2 = Span {
73    ///     file: "test",
74    ///     bytes: (6..8),
75    ///     expanded_from: None,
76    ///     included_from: None
77    /// };
78    /// assert_eq!(span1.compare(&span2), SpanRelation::Earlier)
79    /// ```
80    ///
81    /// Expanded [`Span`]s will be compared starting at their expansion
82    /// point, and working backwards through `#define`s. Included [`Span`]s
83    /// will be compared starting at their first `#include` and working
84    /// through the include hierarchy to their final token. If a [`Span`]
85    /// is included with an `` `include `` directive **AND** expanded from
86    /// a `` `define `` directive, the former is used before the latter.
87    pub fn compare(&self, other: &Self) -> SpanRelation {
88        let mut idx = 0;
89        let self_include_byte_indeces = self.indeces_to_compare();
90        let other_include_byte_indeces = other.indeces_to_compare();
91        loop {
92            match (
93                self_include_byte_indeces.get(idx),
94                other_include_byte_indeces.get(idx),
95            ) {
96                (Some(self_idxs), Some(other_idxs)) => {
97                    let mut nested_idx = 0;
98                    'match_define: loop {
99                        match (
100                            self_idxs.get(nested_idx),
101                            other_idxs.get(nested_idx),
102                        ) {
103                            (Some(self_idx), Some(other_idx)) => {
104                                if self_idx < other_idx {
105                                    return SpanRelation::Earlier;
106                                } else if self_idx > other_idx {
107                                    return SpanRelation::Later;
108                                } else {
109                                    nested_idx += 1;
110                                }
111                            }
112                            (None, None) => {
113                                break 'match_define;
114                            }
115                            _ => {
116                                panic!(
117                                    "Internal error comparing spans {:?} and {:?}",
118                                    self, other
119                                )
120                            }
121                        }
122                    }
123                    idx += 1;
124                }
125                (None, None) => {
126                    break SpanRelation::Same;
127                }
128                _ => {
129                    panic!(
130                        "Internal error comparing spans {:?} and {:?}",
131                        self, other
132                    )
133                }
134            }
135        }
136    }
137
138    /// How many preprocessor macros this [`Span`] was expanded from
139    pub const fn expansion_depth(&self) -> usize {
140        let mut curr_span: &Span = self;
141        let mut depth = 0;
142        loop {
143            if let Some(expanded_from_span) = curr_span.expanded_from {
144                curr_span = expanded_from_span;
145                depth += 1;
146            } else {
147                break;
148            }
149        }
150        depth
151    }
152
153    /// How many `` `include `` macros this [`Span`] was included with
154    pub const fn inclusion_depth(&self) -> usize {
155        let mut curr_span: &Span = self;
156        let mut depth = 0;
157        loop {
158            if let Some(included_from_span) = curr_span.included_from {
159                curr_span = included_from_span;
160                depth += 1;
161            } else {
162                break;
163            }
164        }
165        depth
166    }
167}
168
169#[test]
170fn basic_span_comparison() {
171    let span1 = Span {
172        file: "",
173        bytes: (0..2),
174        expanded_from: None,
175        included_from: None,
176    };
177    let span2 = Span {
178        file: "",
179        bytes: (2..4),
180        expanded_from: None,
181        included_from: None,
182    };
183    assert_eq!(span1.compare(&span2), SpanRelation::Earlier)
184}
185
186#[test]
187fn include_span_comparison() {
188    let span1 = Span {
189        file: "",
190        bytes: (2..4),
191        expanded_from: None,
192        included_from: None,
193    };
194    let included_span = Span {
195        file: "",
196        bytes: (0..2),
197        expanded_from: None,
198        included_from: None,
199    };
200    // Included from an earlier point
201    let span2 = Span {
202        file: "",
203        bytes: (4..6),
204        expanded_from: None,
205        included_from: Some(&included_span),
206    };
207    assert_eq!(span1.compare(&span2), SpanRelation::Later)
208}
209
210#[test]
211fn define_span_comparison() {
212    let span1 = Span {
213        file: "",
214        bytes: (2..4),
215        expanded_from: None,
216        included_from: None,
217    };
218    let definition_span = Span {
219        file: "",
220        bytes: (0..2),
221        expanded_from: None,
222        included_from: None,
223    };
224    // Defined earlier, but should still occur later
225    let span2 = Span {
226        file: "",
227        bytes: (4..6),
228        expanded_from: Some(&definition_span),
229        included_from: None,
230    };
231    assert_eq!(span1.compare(&span2), SpanRelation::Earlier)
232}
233
234#[test]
235fn mixed_define_span_comparison() {
236    let definition_span1 = Span {
237        file: "",
238        bytes: (102..104),
239        expanded_from: None,
240        included_from: None,
241    };
242    let span1 = Span {
243        file: "",
244        bytes: (0..2),
245        expanded_from: Some(&definition_span1),
246        included_from: None,
247    };
248    let definition_span2 = Span {
249        file: "",
250        bytes: (100..102),
251        expanded_from: None,
252        included_from: None,
253    };
254    // Defined earlier, but should still occur later
255    let span2 = Span {
256        file: "",
257        bytes: (2..4),
258        expanded_from: Some(&definition_span2),
259        included_from: None,
260    };
261    assert_eq!(span1.compare(&span2), SpanRelation::Earlier)
262}
263
264#[test]
265fn same_definition_span_comparison() {
266    let definition_span1 = Span {
267        file: "",
268        bytes: (2..4),
269        expanded_from: None,
270        included_from: None,
271    };
272    let span1 = Span {
273        file: "",
274        bytes: (100..104),
275        expanded_from: Some(&definition_span1),
276        included_from: None,
277    };
278    let definition_span2 = Span {
279        file: "",
280        bytes: (0..2),
281        expanded_from: None,
282        included_from: None,
283    };
284    let span2 = Span {
285        file: "",
286        bytes: (100..104),
287        expanded_from: Some(&definition_span2),
288        included_from: None,
289    };
290    assert_eq!(span1.compare(&span2), SpanRelation::Later)
291}
292
293#[test]
294fn define_include_span_comparison() {
295    // Prefer inclusion spots over expansion initially
296    let definition_span1 = Span {
297        file: "",
298        bytes: (2..4),
299        expanded_from: None,
300        included_from: None,
301    };
302    let inclusion_span1 = Span {
303        file: "",
304        bytes: (50..52),
305        expanded_from: None,
306        included_from: None,
307    };
308    let span1 = Span {
309        file: "",
310        bytes: (100..104),
311        expanded_from: Some(&definition_span1),
312        included_from: Some(&inclusion_span1),
313    };
314    let definition_span2 = Span {
315        file: "",
316        bytes: (0..2),
317        expanded_from: None,
318        included_from: None,
319    };
320    let inclusion_span2 = Span {
321        file: "",
322        bytes: (52..54),
323        expanded_from: None,
324        included_from: None,
325    };
326    let span2 = Span {
327        file: "",
328        bytes: (100..104),
329        expanded_from: Some(&definition_span2),
330        included_from: Some(&inclusion_span2),
331    };
332    assert_eq!(span1.compare(&span2), SpanRelation::Earlier)
333}
334
335#[test]
336fn same_define_include_span_comparison() {
337    // Use expansion locations if inclusion spots are the same
338    let definition_span1 = Span {
339        file: "",
340        bytes: (2..4),
341        expanded_from: None,
342        included_from: None,
343    };
344    let inclusion_span1 = Span {
345        file: "",
346        bytes: (50..52),
347        expanded_from: None,
348        included_from: None,
349    };
350    let span1 = Span {
351        file: "",
352        bytes: (100..104),
353        expanded_from: Some(&definition_span1),
354        included_from: Some(&inclusion_span1),
355    };
356    let definition_span2 = Span {
357        file: "",
358        bytes: (0..2),
359        expanded_from: None,
360        included_from: None,
361    };
362    let inclusion_span2 = Span {
363        file: "",
364        bytes: (50..52),
365        expanded_from: None,
366        included_from: None,
367    };
368    let span2 = Span {
369        file: "",
370        bytes: (100..104),
371        expanded_from: Some(&definition_span2),
372        included_from: Some(&inclusion_span2),
373    };
374    assert_eq!(span1.compare(&span2), SpanRelation::Later)
375}
376
377/// Metadata for a given syntax token.
378///
379/// This includes the [`Span`] of the token. With the `lossless` feature,
380/// [`Metadata`] also includes `non_trivia`, which stores non-trivia tokens
381/// such as whitespace and comments - see [`NonTriviaToken`]
382#[cfg(feature = "lossless")]
383#[derive(Default, Clone, Debug)]
384pub struct Metadata<'a> {
385    pub span: Span<'a>,
386    pub non_trivia: Vec<NonTriviaToken<'a>>,
387}
388
389/// Metadata for a given syntax token.
390///
391/// This includes the [`Span`] of the token. With the `lossless` feature,
392/// [`Metadata`] also includes `non_trivia`, which stores non-trivia tokens
393/// such as whitespace and comments - see [`NonTriviaToken`]
394#[cfg(not(feature = "lossless"))]
395#[derive(Default, Clone, Debug)]
396pub struct Metadata<'a> {
397    pub span: Span<'a>,
398}
399
400impl<'a> Metadata<'a> {
401    /// Construct a new [`Metadata`]. If the `lossless` feature isn't enabled,
402    /// `non_trivia` is discarded.
403    #[cfg(feature = "lossless")]
404    pub fn new(span: Span<'a>, non_trivia: Vec<NonTriviaToken<'a>>) -> Self {
405        Self { span, non_trivia }
406    }
407
408    /// Construct a new [`Metadata`]. If the `lossless` feature isn't enabled,
409    /// `non_trivia` is discarded.
410    #[cfg(not(feature = "lossless"))]
411    #[allow(unused_variables)]
412    pub fn new(span: Span<'a>, non_trivia: Vec<NonTriviaToken<'a>>) -> Self {
413        Self { span }
414    }
415}
416
417impl<'a> PartialEq for Metadata<'a> {
418    fn eq(&self, _: &Self) -> bool {
419        // Allows checking of overall CST structure without checking
420        // exact whitespace
421        true
422    }
423}
424
425// Metadata never returns any Nodes while iterating
426impl<'a, 'b> IntoIterator for &'b Metadata<'a> {
427    type Item = Node<'a, 'b>;
428    type IntoIter = std::iter::Empty<Node<'a, 'b>>;
429    fn into_iter(self) -> Self::IntoIter {
430        std::iter::empty()
431    }
432}
433
434impl<'a, 'b> IntoIterator for &'b mut Metadata<'a> {
435    type Item = Node<'a, 'b>;
436    type IntoIter = std::iter::Empty<Node<'a, 'b>>;
437    fn into_iter(self) -> Self::IntoIter {
438        std::iter::empty()
439    }
440}
441
442/// A non-trivia token from the source file
443#[derive(Clone, Debug, PartialEq)]
444pub enum NonTriviaToken<'a> {
445    OnelineComment(&'a str, Span<'a>),
446    BlockComment(&'a str, Span<'a>),
447    Newline,
448}