Fork of daniellemaywood.uk/gleam — Wasm codegen work
2

Configure Feed

Select the types of activity you want to include in your feed.

gleam / compiler-core / src / parse / extra.rs
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}