1#[derive(Clone, Debug, PartialEq, Eq)]
2pub struct ArgInfo {
3 pub name: String,
4 pub position: u8,
5}
6
7#[derive(Clone, Debug, PartialEq, Eq)]
8pub struct DebugFunction {
9 pub name: String,
10 pub args: Vec<ArgInfo>,
11}
12
13#[derive(Clone, Debug, PartialEq, Eq)]
14pub struct LocalInfo {
15 pub name: String,
16 pub index: u8,
17 pub declared_line: Option<u32>,
18 pub last_line: Option<u32>,
19}
20
21#[derive(Clone, Debug, PartialEq, Eq)]
22pub struct LineInfo {
23 pub offset: u32,
24 pub line: u32,
25}
26
27#[derive(Clone, Debug, PartialEq, Eq)]
28pub struct DebugInfo {
29 pub source: Option<String>,
30 pub lines: Vec<LineInfo>,
31 pub functions: Vec<DebugFunction>,
32 pub locals: Vec<LocalInfo>,
33}
34
35impl DebugInfo {
36 pub fn line_for_offset(&self, offset: usize) -> Option<u32> {
37 let offset = offset as u32;
38 if self.lines.is_empty() {
39 return None;
40 }
41 let mut lo = 0;
42 let mut hi = self.lines.len();
43 while lo < hi {
44 let mid = (lo + hi) / 2;
45 if self.lines[mid].offset <= offset {
46 lo = mid + 1;
47 } else {
48 hi = mid;
49 }
50 }
51 if lo == 0 {
52 None
53 } else {
54 Some(self.lines[lo - 1].line)
55 }
56 }
57
58 pub fn offsets_for_line(&self, line: u32) -> Vec<u32> {
59 self.lines
60 .iter()
61 .filter(|info| info.line == line)
62 .map(|info| info.offset)
63 .collect()
64 }
65
66 pub fn source_line(&self, line: u32) -> Option<String> {
67 let source = self.source.as_ref()?;
68 let index = line.checked_sub(1)? as usize;
69 source.lines().nth(index).map(|text| text.to_string())
70 }
71
72 pub fn local_index(&self, name: &str) -> Option<u8> {
73 self.locals
74 .iter()
75 .find(|local| local.name == name)
76 .map(|local| local.index)
77 }
78}
79
80#[derive(Default)]
81pub struct DebugInfoBuilder {
82 source: Option<String>,
83 lines: Vec<LineInfo>,
84 functions: Vec<DebugFunction>,
85 locals: Vec<LocalInfo>,
86 last_offset: Option<u32>,
87}
88
89impl DebugInfoBuilder {
90 pub fn new() -> Self {
91 Self::default()
92 }
93
94 pub fn set_source(&mut self, source: String) {
95 self.source = Some(source);
96 }
97
98 pub fn add_function(&mut self, name: String, args: Vec<String>) {
99 let args = args
100 .into_iter()
101 .enumerate()
102 .map(|(idx, name)| ArgInfo {
103 name,
104 position: idx as u8,
105 })
106 .collect();
107 self.functions.push(DebugFunction { name, args });
108 }
109
110 pub fn add_local(&mut self, name: String, index: u8) {
111 self.add_local_with_range(name, index, None, None);
112 }
113
114 pub fn add_local_with_range(
115 &mut self,
116 name: String,
117 index: u8,
118 declared_line: Option<u32>,
119 last_line: Option<u32>,
120 ) {
121 if let Some(existing) = self.locals.iter_mut().find(|local| local.name == name) {
122 existing.index = index;
123 existing.declared_line = merge_min_line(existing.declared_line, declared_line);
124 existing.last_line = merge_max_line(existing.last_line, last_line);
125 return;
126 }
127 self.locals.push(LocalInfo {
128 name,
129 index,
130 declared_line,
131 last_line,
132 });
133 }
134
135 pub fn mark_line(&mut self, offset: u32, line: u32) {
136 if self.last_offset == Some(offset) {
137 if let Some(last) = self.lines.last_mut()
138 && last.offset == offset
139 {
140 last.line = line;
143 }
144 return;
145 }
146 self.lines.push(LineInfo { offset, line });
147 self.last_offset = Some(offset);
148 }
149
150 pub fn finish(self) -> Option<DebugInfo> {
151 if self.source.is_none()
152 && self.lines.is_empty()
153 && self.functions.is_empty()
154 && self.locals.is_empty()
155 {
156 return None;
157 }
158 Some(DebugInfo {
159 source: self.source,
160 lines: self.lines,
161 functions: self.functions,
162 locals: self.locals,
163 })
164 }
165}
166
167fn merge_min_line(current: Option<u32>, incoming: Option<u32>) -> Option<u32> {
168 match (current, incoming) {
169 (Some(lhs), Some(rhs)) => Some(lhs.min(rhs)),
170 (Some(lhs), None) => Some(lhs),
171 (None, Some(rhs)) => Some(rhs),
172 (None, None) => None,
173 }
174}
175
176fn merge_max_line(current: Option<u32>, incoming: Option<u32>) -> Option<u32> {
177 match (current, incoming) {
178 (Some(lhs), Some(rhs)) => Some(lhs.max(rhs)),
179 (Some(lhs), None) => Some(lhs),
180 (None, Some(rhs)) => Some(rhs),
181 (None, None) => None,
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use super::DebugInfoBuilder;
188
189 #[test]
190 fn add_local_with_range_keeps_distinct_names_on_shared_slot() {
191 let mut builder = DebugInfoBuilder::new();
192 builder.add_local_with_range("a".to_string(), 0, Some(1), Some(1));
193 builder.add_local_with_range("b".to_string(), 0, Some(2), Some(2));
194
195 let debug = builder.finish().expect("debug info should be present");
196 assert_eq!(debug.locals.len(), 2);
197 assert_eq!(debug.locals[0].name, "a");
198 assert_eq!(debug.locals[1].name, "b");
199 }
200}