Fork of daniellemaywood.uk/gleam — Wasm codegen work
6.0 kB
195 lines
1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2021 The Gleam contributors
3
4use std::cmp::Ordering;
5
6use ecow::EcoString;
7
8use crate::ast::SrcSpan;
9
10#[derive(Debug, PartialEq, Eq, Default)]
11pub struct ModuleExtra {
12 pub module_comments: Vec<SrcSpan>,
13 pub doc_comments: Vec<SrcSpan>,
14 pub comments: Vec<SrcSpan>,
15 pub empty_lines: Vec<u32>,
16 pub new_lines: Vec<u32>,
17 pub trailing_commas: Vec<u32>,
18}
19
20impl ModuleExtra {
21 pub fn new() -> Self {
22 Self::default()
23 }
24
25 /// Detects if a byte index is in a comment context
26 pub fn is_within_comment(&self, byte_index: u32) -> bool {
27 let cmp = |span: &SrcSpan| {
28 if byte_index < span.start {
29 Ordering::Greater
30 } else if byte_index > span.end {
31 Ordering::Less
32 } else {
33 Ordering::Equal
34 }
35 };
36
37 self.comments.binary_search_by(cmp).is_ok()
38 || self.doc_comments.binary_search_by(cmp).is_ok()
39 || self.module_comments.binary_search_by(cmp).is_ok()
40 }
41
42 pub fn has_comment_between(&self, start: u32, end: u32) -> bool {
43 self.first_comment_between(start, end).is_some()
44 }
45
46 fn index_of_first_comment_between(&self, start: u32, end: u32) -> Option<usize> {
47 self.comments
48 .binary_search_by(|comment| {
49 if comment.end < start {
50 Ordering::Less
51 } else if comment.start > end {
52 Ordering::Greater
53 } else {
54 Ordering::Equal
55 }
56 })
57 .ok()
58 }
59
60 /// Returns the first comment overlapping the given source locations (inclusive)
61 /// Note that the returned span covers the text of the comment, not the `//`
62 pub fn first_comment_between(&self, start: u32, end: u32) -> Option<SrcSpan> {
63 // Index of a comment, but not necessarily the first one.
64 let index = self.index_of_first_comment_between(start, end)?;
65
66 self.comments
67 .get(0..=index)?
68 .iter()
69 .rev()
70 .take_while(|comment| {
71 // comment overlaps span (inclusive of endpoints)
72 comment.end >= start && comment.start <= end
73 })
74 .last()
75 .copied()
76 }
77
78 pub fn last_comment_between(&self, start: u32, end: u32) -> Option<SrcSpan> {
79 // We start from the first comment that we can find in between the given
80 // start and end, this is really fast as we can find such index through
81 // binary search.
82 let mut index_of_last_comment = self.index_of_first_comment_between(start, end)?;
83
84 // Then we go over all the comments we can find from that one that are
85 // still in between the given indices.
86 loop {
87 let next_comment = self.comments.get(index_of_last_comment + 1);
88 if let Some(next_comment) = next_comment
89 && start <= next_comment.start
90 && next_comment.end <= end
91 {
92 // The next comment is still in between the two indices, we keep
93 // going.
94 index_of_last_comment += 1;
95 } else {
96 // The next comment is outside of the given range, that means
97 // the current one is the last comment we were looking for.
98 // We can return it!
99 return self.comments.get(index_of_last_comment).cloned();
100 }
101 }
102 }
103}
104
105#[derive(Debug, PartialEq, Eq)]
106pub struct Comment<'a> {
107 pub start: u32,
108 pub content: &'a str,
109}
110
111impl<'a> From<(&SrcSpan, &'a EcoString)> for Comment<'a> {
112 fn from(value: (&SrcSpan, &'a EcoString)) -> Self {
113 Self::from((value.0, value.1.as_str()))
114 }
115}
116
117impl<'a> From<(&SrcSpan, &'a str)> for Comment<'a> {
118 fn from(src: (&SrcSpan, &'a str)) -> Comment<'a> {
119 let start = src.0.start;
120 let end = src.0.end as usize;
121 Comment {
122 start,
123 content: src
124 .1
125 .get(start as usize..end)
126 .expect("From span to comment"),
127 }
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use crate::{ast::SrcSpan, parse::extra::ModuleExtra};
134
135 fn set_up_extra() -> ModuleExtra {
136 let mut extra = ModuleExtra::new();
137 extra.comments = vec![
138 SrcSpan { start: 0, end: 10 },
139 SrcSpan { start: 20, end: 30 },
140 SrcSpan { start: 40, end: 50 },
141 SrcSpan { start: 60, end: 70 },
142 SrcSpan { start: 80, end: 90 },
143 SrcSpan {
144 start: 90,
145 end: 100,
146 },
147 ];
148 extra
149 }
150
151 #[test]
152 fn first_comment_between() {
153 let extra = set_up_extra();
154 assert!(matches!(
155 extra.first_comment_between(15, 85),
156 Some(SrcSpan { start: 20, end: 30 })
157 ));
158 }
159
160 #[test]
161 fn first_comment_between_equal_to_range() {
162 let extra = set_up_extra();
163 assert!(matches!(
164 extra.first_comment_between(40, 50),
165 Some(SrcSpan { start: 40, end: 50 })
166 ));
167 }
168
169 #[test]
170 fn first_comment_between_overlapping_start_of_range() {
171 let extra = set_up_extra();
172 assert!(matches!(
173 extra.first_comment_between(45, 80),
174 Some(SrcSpan { start: 40, end: 50 })
175 ));
176 }
177
178 #[test]
179 fn first_comment_between_overlapping_end_of_range() {
180 let extra = set_up_extra();
181 assert!(matches!(
182 extra.first_comment_between(35, 45),
183 Some(SrcSpan { start: 40, end: 50 })
184 ));
185 }
186
187 #[test]
188 fn first_comment_between_at_end_of_range() {
189 let extra = set_up_extra();
190 assert!(matches!(
191 extra.first_comment_between(55, 60),
192 Some(SrcSpan { start: 60, end: 70 })
193 ));
194 }
195}