1use srcmap_codec::{vlq_decode, vlq_decode_unsigned};
6
7use crate::{
8 Binding, CallSite, GeneratedRange, OriginalScope, Position, ScopeInfo, ScopesError,
9 SubRangeBinding, TAG_GENERATED_RANGE_BINDINGS, TAG_GENERATED_RANGE_CALL_SITE,
10 TAG_GENERATED_RANGE_END, TAG_GENERATED_RANGE_START, TAG_GENERATED_RANGE_SUB_RANGE_BINDINGS,
11 TAG_ORIGINAL_SCOPE_END, TAG_ORIGINAL_SCOPE_START, TAG_ORIGINAL_SCOPE_VARIABLES,
12 resolve_binding, resolve_name,
13};
14
15struct Tokenizer<'a> {
18 input: &'a [u8],
19 pos: usize,
20}
21
22impl<'a> Tokenizer<'a> {
23 fn new(input: &'a [u8]) -> Self {
24 Self { input, pos: 0 }
25 }
26
27 #[inline]
28 fn has_next(&self) -> bool {
29 self.pos < self.input.len()
30 }
31
32 #[inline]
34 fn at_item_end(&self) -> bool {
35 self.pos >= self.input.len() || self.input[self.pos] == b','
36 }
37
38 #[inline]
40 fn skip_comma(&mut self) {
41 if self.pos < self.input.len() && self.input[self.pos] == b',' {
42 self.pos += 1;
43 }
44 }
45
46 #[inline]
47 fn read_unsigned(&mut self) -> Result<u64, ScopesError> {
48 let (val, consumed) = vlq_decode_unsigned(self.input, self.pos)?;
49 self.pos += consumed;
50 Ok(val)
51 }
52
53 #[inline]
54 fn read_signed(&mut self) -> Result<i64, ScopesError> {
55 let (val, consumed) = vlq_decode(self.input, self.pos)?;
56 self.pos += consumed;
57 Ok(val)
58 }
59}
60
61struct BuildingScope {
64 start: Position,
65 name: Option<String>,
66 kind: Option<String>,
67 is_stack_frame: bool,
68 variables: Vec<String>,
69 children: Vec<OriginalScope>,
70}
71
72struct BuildingRange {
73 start: Position,
74 is_stack_frame: bool,
75 is_hidden: bool,
76 definition: Option<u32>,
77 call_site: Option<CallSite>,
78 bindings: Vec<Binding>,
79 sub_range_bindings: Vec<(usize, Vec<SubRangeBinding>)>,
80 children: Vec<GeneratedRange>,
81}
82
83pub fn decode_scopes(
93 input: &str,
94 names: &[String],
95 num_sources: usize,
96) -> Result<ScopeInfo, ScopesError> {
97 if input.is_empty() {
98 let scopes = vec![None; num_sources];
99 return Ok(ScopeInfo { scopes, ranges: vec![] });
100 }
101
102 let mut tok = Tokenizer::new(input.as_bytes());
103
104 let mut scopes: Vec<Option<OriginalScope>> = Vec::new();
106 let mut source_idx = 0usize;
107 let mut scope_stack: Vec<BuildingScope> = Vec::new();
108 let mut os_line = 0u32;
109 let mut os_col = 0u32;
110 let mut os_name = 0i64;
111 let mut os_kind = 0i64;
112 let mut os_var = 0i64;
113
114 let mut ranges: Vec<GeneratedRange> = Vec::new();
116 let mut range_stack: Vec<BuildingRange> = Vec::new();
117 let mut gr_line = 0u32;
118 let mut gr_col = 0u32;
119 let mut gr_def = 0i64;
120 let mut h_var_acc: u64 = 0;
121 let mut in_generated_ranges = false;
122
123 while tok.has_next() {
124 if tok.at_item_end() {
126 if !in_generated_ranges && source_idx < num_sources && scope_stack.is_empty() {
127 scopes.push(None);
128 source_idx += 1;
129 }
130 tok.skip_comma();
131 continue;
132 }
133
134 let tag = tok.read_unsigned()?;
135
136 match tag {
137 TAG_ORIGINAL_SCOPE_START => {
138 if scope_stack.is_empty() {
140 os_line = 0;
141 os_col = 0;
142 }
143
144 let flags = tok.read_unsigned()?;
145
146 let line_delta = tok.read_unsigned()? as u32;
147 os_line += line_delta;
148 let col_raw = tok.read_unsigned()? as u32;
149 os_col = if line_delta != 0 { col_raw } else { os_col + col_raw };
150
151 let name = if flags & crate::OS_FLAG_HAS_NAME != 0 {
152 let d = tok.read_signed()?;
153 os_name += d;
154 Some(resolve_name(names, os_name)?)
155 } else {
156 None
157 };
158
159 let kind = if flags & crate::OS_FLAG_HAS_KIND != 0 {
160 let d = tok.read_signed()?;
161 os_kind += d;
162 Some(resolve_name(names, os_kind)?)
163 } else {
164 None
165 };
166
167 let is_stack_frame = flags & crate::OS_FLAG_IS_STACK_FRAME != 0;
168
169 scope_stack.push(BuildingScope {
170 start: Position { line: os_line, column: os_col },
171 name,
172 kind,
173 is_stack_frame,
174 variables: Vec::new(),
175 children: Vec::new(),
176 });
177 }
178
179 TAG_ORIGINAL_SCOPE_END => {
180 if scope_stack.is_empty() {
181 return Err(ScopesError::UnmatchedScopeEnd);
182 }
183
184 let line_delta = tok.read_unsigned()? as u32;
185 os_line += line_delta;
186 let col_raw = tok.read_unsigned()? as u32;
187 os_col = if line_delta != 0 { col_raw } else { os_col + col_raw };
188
189 let building = scope_stack.pop().expect("non-empty: checked above");
191 let finished = OriginalScope {
192 start: building.start,
193 end: Position { line: os_line, column: os_col },
194 name: building.name,
195 kind: building.kind,
196 is_stack_frame: building.is_stack_frame,
197 variables: building.variables,
198 children: building.children,
199 };
200
201 if scope_stack.is_empty() {
202 scopes.push(Some(finished));
203 source_idx += 1;
204 } else {
205 scope_stack
207 .last_mut()
208 .expect("non-empty: checked above")
209 .children
210 .push(finished);
211 }
212 }
213
214 TAG_ORIGINAL_SCOPE_VARIABLES => {
215 if let Some(current) = scope_stack.last_mut() {
216 while !tok.at_item_end() {
217 let d = tok.read_signed()?;
218 os_var += d;
219 current.variables.push(resolve_name(names, os_var)?);
220 }
221 } else {
222 while !tok.at_item_end() {
223 let _ = tok.read_signed()?;
224 }
225 }
226 }
227
228 TAG_GENERATED_RANGE_START => {
229 if !in_generated_ranges {
230 in_generated_ranges = true;
231 while scopes.len() < num_sources {
233 scopes.push(None);
234 }
235 source_idx = num_sources;
236 }
237
238 let flags = tok.read_unsigned()?;
239
240 let line_delta = if flags & crate::GR_FLAG_HAS_LINE != 0 {
241 tok.read_unsigned()? as u32
242 } else {
243 0
244 };
245 gr_line += line_delta;
246
247 let col_raw = tok.read_unsigned()? as u32;
248 gr_col = if line_delta != 0 { col_raw } else { gr_col + col_raw };
249
250 let definition = if flags & crate::GR_FLAG_HAS_DEFINITION != 0 {
251 let d = tok.read_signed()?;
252 gr_def += d;
253 Some(gr_def as u32)
254 } else {
255 None
256 };
257
258 let is_stack_frame = flags & crate::GR_FLAG_IS_STACK_FRAME != 0;
259 let is_hidden = flags & crate::GR_FLAG_IS_HIDDEN != 0;
260
261 h_var_acc = 0;
263
264 range_stack.push(BuildingRange {
265 start: Position { line: gr_line, column: gr_col },
266 is_stack_frame,
267 is_hidden,
268 definition,
269 call_site: None,
270 bindings: Vec::new(),
271 sub_range_bindings: Vec::new(),
272 children: Vec::new(),
273 });
274 }
275
276 TAG_GENERATED_RANGE_END => {
277 if range_stack.is_empty() {
278 return Err(ScopesError::UnmatchedRangeEnd);
279 }
280
281 let first = tok.read_unsigned()? as u32;
283 let (line_delta, col_raw) = if !tok.at_item_end() {
284 let second = tok.read_unsigned()? as u32;
285 (first, second)
286 } else {
287 (0, first)
288 };
289 gr_line += line_delta;
290 gr_col = if line_delta != 0 { col_raw } else { gr_col + col_raw };
291
292 let building = range_stack.pop().expect("non-empty: checked above");
294
295 let final_bindings =
297 merge_bindings(building.bindings, &building.sub_range_bindings, building.start);
298
299 let finished = GeneratedRange {
300 start: building.start,
301 end: Position { line: gr_line, column: gr_col },
302 is_stack_frame: building.is_stack_frame,
303 is_hidden: building.is_hidden,
304 definition: building.definition,
305 call_site: building.call_site,
306 bindings: final_bindings,
307 children: building.children,
308 };
309
310 if range_stack.is_empty() {
311 ranges.push(finished);
312 } else {
313 range_stack
315 .last_mut()
316 .expect("non-empty: checked above")
317 .children
318 .push(finished);
319 }
320 }
321
322 TAG_GENERATED_RANGE_BINDINGS => {
323 if let Some(current) = range_stack.last_mut() {
324 while !tok.at_item_end() {
325 let idx = tok.read_unsigned()?;
326 let binding = match resolve_binding(names, idx)? {
327 Some(expr) => Binding::Expression(expr),
328 None => Binding::Unavailable,
329 };
330 current.bindings.push(binding);
331 }
332 } else {
333 while !tok.at_item_end() {
334 let _ = tok.read_unsigned()?;
335 }
336 }
337 }
338
339 TAG_GENERATED_RANGE_SUB_RANGE_BINDINGS => {
340 if let Some(current) = range_stack.last_mut() {
341 let var_delta = tok.read_unsigned()?;
342 h_var_acc += var_delta;
343 let var_idx = h_var_acc as usize;
344
345 let mut sub_ranges: Vec<SubRangeBinding> = Vec::new();
346 let mut h_line = current.start.line;
348 let mut h_col = current.start.column;
349
350 while !tok.at_item_end() {
351 let binding_raw = tok.read_unsigned()?;
352 let line_delta = tok.read_unsigned()? as u32;
353 h_line += line_delta;
354
355 let col_raw = tok.read_unsigned()? as u32;
356 h_col = if line_delta != 0 { col_raw } else { h_col + col_raw };
357
358 let expression = resolve_binding(names, binding_raw)?;
359 sub_ranges.push(SubRangeBinding {
360 expression,
361 from: Position { line: h_line, column: h_col },
362 });
363 }
364
365 current.sub_range_bindings.push((var_idx, sub_ranges));
366 } else {
367 while !tok.at_item_end() {
368 let _ = tok.read_unsigned()?;
369 }
370 }
371 }
372
373 TAG_GENERATED_RANGE_CALL_SITE => {
374 if let Some(current) = range_stack.last_mut() {
375 let source_index = tok.read_unsigned()? as u32;
376 let line = tok.read_unsigned()? as u32;
377 let column = tok.read_unsigned()? as u32;
378 current.call_site = Some(CallSite { source_index, line, column });
379 } else {
380 while !tok.at_item_end() {
381 let _ = tok.read_unsigned()?;
382 }
383 }
384 }
385
386 _ => {
387 while !tok.at_item_end() {
389 let _ = tok.read_unsigned()?;
390 }
391 }
392 }
393
394 tok.skip_comma();
395 }
396
397 if !scope_stack.is_empty() {
398 return Err(ScopesError::UnclosedScope);
399 }
400 if !range_stack.is_empty() {
401 return Err(ScopesError::UnclosedRange);
402 }
403
404 while scopes.len() < num_sources {
406 scopes.push(None);
407 }
408
409 Ok(ScopeInfo { scopes, ranges })
410}
411
412fn merge_bindings(
414 initial: Vec<Binding>,
415 sub_range_map: &[(usize, Vec<SubRangeBinding>)],
416 range_start: Position,
417) -> Vec<Binding> {
418 if sub_range_map.is_empty() {
419 return initial;
420 }
421
422 let mut result = initial;
423
424 for (var_idx, sub_ranges) in sub_range_map {
425 if *var_idx < result.len() {
426 let initial_expr = match &result[*var_idx] {
428 Binding::Expression(e) => Some(e.clone()),
429 Binding::Unavailable | Binding::SubRanges(_) => None, };
431
432 let mut all_subs =
433 vec![SubRangeBinding { expression: initial_expr, from: range_start }];
434 all_subs.extend(sub_ranges.iter().cloned());
435
436 result[*var_idx] = Binding::SubRanges(all_subs);
437 }
438 }
439
440 result
441}