1use ownsight_core::*;
2use anyhow::Result;
3use std::collections::HashMap;
4
5pub struct SimpleAnalyzer {
6 mode: AnalysisMode,
7}
8
9impl SimpleAnalyzer {
10 pub fn new(mode: AnalysisMode) -> Self {
11 Self { mode }
12 }
13
14 pub fn analyze(&mut self, source: &str, filename: &str) -> Result<ProgramAnalysis> {
15 let mut analyzer = Analyzer::new(self.mode.clone());
16
17 analyzer.analyze_snippet(source, filename)?;
18
19 let parsed = self.parse_simple_rust(source, filename)?;
20
21 analyzer.analysis.variables = parsed.variables;
22 analyzer.analysis.events = parsed.events;
23 analyzer.analysis.scopes = parsed.scopes;
24 analyzer.analysis.functions = parsed.functions;
25
26 Ok(analyzer.finalize())
27 }
28
29 fn parse_simple_rust(&self, source: &str, filename: &str) -> Result<ParsedProgram> {
30 let mut parsed = ParsedProgram::default();
31 let lines: Vec<&str> = source.lines().collect();
32
33 let mut var_counter = 0;
34 let _event_counter = 0;
35 let mut var_map: HashMap<String, VariableId> = HashMap::new();
36 let mut event_builder = EventBuilder::new();
37
38 let root_scope = Scope {
39 id: ScopeId(0),
40 parent: None,
41 start_line: 1,
42 end_line: lines.len(),
43 kind: ScopeKind::Function,
44 };
45 parsed.scopes.push(root_scope);
46
47 for (line_idx, line) in lines.iter().enumerate() {
48 let line_num = line_idx + 1;
49 let trimmed = line.trim();
50
51 if trimmed.starts_with("let ") {
52 if let Some(var_info) = self.parse_let_statement(trimmed, line_num, filename) {
53 let var_id = VariableId(var_counter);
54 var_counter += 1;
55
56 let variable = Variable {
57 id: var_id,
58 name: var_info.name.clone(),
59 ty: var_info.ty.clone(),
60 scope_id: ScopeId(0),
61 span: Span::single_line(filename.to_string(), line_num, 0, line.len()),
62 is_mutable: var_info.is_mutable,
63 };
64
65 var_map.insert(var_info.name.clone(), var_id);
66 parsed.variables.push(variable);
67
68 let create_event = event_builder.create_detailed_event(
69 EventKind::Create,
70 var_id,
71 Span::single_line(filename.to_string(), line_num, 0, line.len()),
72 line_num,
73 &var_info.name,
74 None,
75 );
76 parsed.events.push(create_event);
77
78 if trimmed.contains("&mut ") {
79 if let Some(borrowed_name) = self.extract_borrowed_var(trimmed) {
80 if let Some(&borrowed_id) = var_map.get(&borrowed_name) {
81 let mut borrow_event = event_builder.create_detailed_event(
82 EventKind::BorrowMut,
83 borrowed_id,
84 Span::single_line(filename.to_string(), line_num, 0, line.len()),
85 line_num,
86 &borrowed_name,
87 None,
88 );
89 borrow_event.related_variable_id = Some(var_id);
90 parsed.events.push(borrow_event);
91 }
92 }
93 } else if trimmed.contains("&") && !trimmed.contains("&mut") {
94 if let Some(borrowed_name) = self.extract_borrowed_var(trimmed) {
95 if let Some(&borrowed_id) = var_map.get(&borrowed_name) {
96 let mut borrow_event = event_builder.create_detailed_event(
97 EventKind::BorrowShared,
98 borrowed_id,
99 Span::single_line(filename.to_string(), line_num, 0, line.len()),
100 line_num,
101 &borrowed_name,
102 None,
103 );
104 borrow_event.related_variable_id = Some(var_id);
105 parsed.events.push(borrow_event);
106 }
107 }
108 }
109 }
110 }
111
112 if let Some(func_call) = self.parse_function_call(trimmed) {
113 for arg in &func_call.args {
114 if let Some(&var_id) = var_map.get(arg) {
115 if !trimmed.contains(&format!("&{}", arg)) {
116 let move_event = event_builder.create_detailed_event(
117 EventKind::MoveOut,
118 var_id,
119 Span::single_line(filename.to_string(), line_num, 0, line.len()),
120 line_num,
121 arg,
122 Some(format!(
123 "`{}` was moved into function `{}`. It cannot be used after this point.",
124 arg, func_call.name
125 )),
126 );
127 parsed.events.push(move_event);
128 }
129 }
130 }
131 }
132
133 if trimmed == "}" {
134 for (var_name, &var_id) in &var_map {
135 let last_event = parsed.events.iter()
136 .filter(|e| e.variable_id == var_id)
137 .last();
138
139 if let Some(last) = last_event {
140 if last.kind != EventKind::MoveOut && last.kind != EventKind::Drop {
141 let drop_event = event_builder.create_detailed_event(
142 EventKind::Drop,
143 var_id,
144 Span::single_line(filename.to_string(), line_num, 0, line.len()),
145 line_num,
146 var_name,
147 None,
148 );
149 parsed.events.push(drop_event);
150 }
151 }
152 }
153 }
154 }
155
156 Ok(parsed)
157 }
158
159 fn parse_let_statement(&self, line: &str, _line_num: usize, _filename: &str) -> Option<VarInfo> {
160 let is_mutable = line.contains("let mut ");
161 let after_let = if is_mutable {
162 line.strip_prefix("let mut ")?.trim()
163 } else {
164 line.strip_prefix("let ")?.trim()
165 };
166
167 let parts: Vec<&str> = after_let.split('=').collect();
168 if parts.is_empty() {
169 return None;
170 }
171
172 let var_part = parts[0].trim();
173 let name_and_type: Vec<&str> = var_part.split(':').collect();
174
175 let name = name_and_type[0].trim().to_string();
176 let ty = if name_and_type.len() > 1 {
177 name_and_type[1].trim().to_string()
178 } else {
179 "inferred".to_string()
180 };
181
182 Some(VarInfo { name, ty, is_mutable })
183 }
184
185 fn extract_borrowed_var(&self, line: &str) -> Option<String> {
186 if let Some(pos) = line.find("&mut ") {
187 let after = &line[pos + 5..];
188 let var_name = after.split(|c: char| !c.is_alphanumeric() && c != '_')
189 .next()?
190 .trim();
191 return Some(var_name.to_string());
192 } else if let Some(pos) = line.find("&") {
193 let after = &line[pos + 1..];
194 let var_name = after.split(|c: char| !c.is_alphanumeric() && c != '_')
195 .next()?
196 .trim();
197 if !var_name.is_empty() && var_name != "mut" {
198 return Some(var_name.to_string());
199 }
200 }
201 None
202 }
203
204 fn parse_function_call(&self, line: &str) -> Option<FunctionCall> {
205 if !line.contains('(') || !line.contains(')') {
206 return None;
207 }
208
209 let parts: Vec<&str> = line.split('(').collect();
210 if parts.len() < 2 {
211 return None;
212 }
213
214 let func_name = parts[0].trim().split_whitespace().last()?.to_string();
215
216 let args_part = parts[1].split(')').next()?;
217 let args: Vec<String> = args_part
218 .split(',')
219 .map(|s| s.trim())
220 .filter(|s| !s.is_empty())
221 .map(|s| {
222 s.trim_start_matches('&')
223 .trim_start_matches("mut ")
224 .trim()
225 .to_string()
226 })
227 .collect();
228
229 Some(FunctionCall {
230 name: func_name,
231 args,
232 })
233 }
234}
235
236#[derive(Default)]
237struct ParsedProgram {
238 variables: Vec<Variable>,
239 events: Vec<Event>,
240 scopes: Vec<Scope>,
241 functions: Vec<FunctionInfo>,
242}
243
244struct VarInfo {
245 name: String,
246 ty: String,
247 is_mutable: bool,
248}
249
250struct FunctionCall {
251 name: String,
252 args: Vec<String>,
253}