js_source_scopes/
scope_index.rs1use std::ops::Range;
2
3use indexmap::IndexSet;
4
5#[derive(Debug)]
29pub struct ScopeIndex {
30 names: IndexSet<String>,
31 ranges: Vec<(u32, u32)>,
33}
34
35impl ScopeIndex {
36 #[tracing::instrument(level = "trace", name = "ScopeIndex::new", skip_all)]
38 pub fn new(mut scopes: Vec<(Range<u32>, Option<String>)>) -> Result<Self, ScopeIndexError> {
39 let mut names = IndexSet::new();
40 let mut ranges = vec![];
41
42 scopes.sort_by_key(|s| s.0.start);
43
44 let needs_zero = scopes.first().map(|s| s.0.start != 0).unwrap_or(false);
45 if needs_zero {
46 ranges.push((0, GLOBAL_SCOPE_SENTINEL));
47 }
48
49 let mut stack: Vec<(Range<u32>, u32)> = vec![];
50
51 for (range, name) in scopes {
52 unwind_scope_stack(&mut ranges, &mut stack, range.clone())?;
53
54 let name_idx = match name {
55 Some(name) => names
56 .insert_full(name)
57 .0
58 .try_into()
59 .map_err(|_| ScopeIndexError(()))?,
60 None => ANONYMOUS_SCOPE_SENTINEL,
61 };
62
63 ranges.push((range.start, name_idx));
64
65 if let Some(last) = stack.last() {
66 if last.0.end == range.end {
67 stack.pop();
68 }
69 }
70 stack.push((range, name_idx));
71 }
72
73 while let Some(last) = stack.pop() {
75 let name_idx = stack
77 .last()
78 .map(|prev| prev.1)
79 .unwrap_or(GLOBAL_SCOPE_SENTINEL);
80 ranges.push((last.0.end, name_idx));
81 }
82
83 Ok(Self { names, ranges })
84 }
85
86 pub fn lookup(&self, offset: u32) -> ScopeLookupResult {
88 let range_idx = match self.ranges.binary_search_by_key(&offset, |r| r.0) {
89 Ok(idx) => idx,
90 Err(0) => 0, Err(idx) => idx - 1,
92 };
93
94 let name_idx = match self.ranges.get(range_idx) {
95 Some(r) => r.1,
96 None => return ScopeLookupResult::Unknown,
97 };
98
99 self.resolve_name(name_idx)
100 }
101
102 fn resolve_name(&self, name_idx: u32) -> ScopeLookupResult {
103 if name_idx == GLOBAL_SCOPE_SENTINEL {
104 ScopeLookupResult::Unknown
105 } else if name_idx == ANONYMOUS_SCOPE_SENTINEL {
106 ScopeLookupResult::AnonymousScope
107 } else {
108 match self.names.get_index(name_idx as usize) {
109 Some(name) => ScopeLookupResult::NamedScope(name.as_str()),
110 None => ScopeLookupResult::Unknown,
111 }
112 }
113 }
114
115 pub fn iter(&self) -> impl Iterator<Item = (u32, ScopeLookupResult)> {
120 self.ranges.iter().map(|r| (r.0, self.resolve_name(r.1)))
121 }
122}
123
124#[derive(Clone, Copy, Debug, PartialEq, Eq)]
126pub enum ScopeLookupResult<'data> {
127 NamedScope(&'data str),
129 AnonymousScope,
131 Unknown,
135}
136
137fn unwind_scope_stack(
140 ranges: &mut Vec<(u32, u32)>,
141 stack: &mut Vec<(Range<u32>, u32)>,
142 range: Range<u32>,
143) -> Result<(), ScopeIndexError> {
144 while let Some(last) = stack.pop() {
145 if last.0.end <= range.start {
147 let name_idx = stack
148 .last()
149 .map(|prev| prev.1)
150 .unwrap_or(GLOBAL_SCOPE_SENTINEL);
151 ranges.push((last.0.end, name_idx));
152 } else if last.0.end < range.end {
153 return Err(ScopeIndexError(()));
155 } else {
156 stack.push(last);
158 return Ok(());
159 }
160 }
161 Ok(())
162}
163
164pub(crate) const GLOBAL_SCOPE_SENTINEL: u32 = u32::MAX;
165pub(crate) const ANONYMOUS_SCOPE_SENTINEL: u32 = u32::MAX - 1;
166
167#[derive(Debug)]
169pub struct ScopeIndexError(());
170
171impl std::error::Error for ScopeIndexError {}
172
173impl std::fmt::Display for ScopeIndexError {
174 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
175 f.write_str("source could not be converted to source context")
176 }
177}
178
179#[cfg(test)]
180mod tests {
181 use super::*;
182
183 #[test]
184 fn invalid_nesting() {
185 let scopes = vec![(0..10, None), (5..15, None)];
186 assert!(ScopeIndex::new(scopes).is_err());
187 }
188
189 #[test]
190 fn scope_index() {
191 let scopes = vec![
192 (5..25, Some(String::from("parent"))),
193 (10..15, Some(String::from("child"))),
194 (20..25, Some(String::from("child2"))),
195 (30..50, None),
196 ];
197
198 let idx = ScopeIndex::new(scopes).unwrap();
199
200 assert_eq!(idx.lookup(3), ScopeLookupResult::Unknown);
201 assert_eq!(idx.lookup(7), ScopeLookupResult::NamedScope("parent"));
202 assert_eq!(idx.lookup(12), ScopeLookupResult::NamedScope("child"));
203 assert_eq!(idx.lookup(17), ScopeLookupResult::NamedScope("parent"));
204 assert_eq!(idx.lookup(22), ScopeLookupResult::NamedScope("child2"));
205 assert_eq!(idx.lookup(25), ScopeLookupResult::Unknown);
206 assert_eq!(idx.lookup(30), ScopeLookupResult::AnonymousScope);
207 assert_eq!(idx.lookup(50), ScopeLookupResult::Unknown);
208 }
209}