diff options
author | Minijackson <minijackson@riseup.net> | 2019-12-18 20:56:53 +0100 |
---|---|---|
committer | Minijackson <minijackson@riseup.net> | 2019-12-18 20:56:53 +0100 |
commit | de896baff7e97fac4dde79078c9a2fa1c652576b (patch) | |
tree | 512b67b91d64e51d63f7ac5ff925a5c96d9aaf3c /src/parsing/clang/parsing.rs | |
parent | 860b73f1644ecd6548ae403ec483625fb7b625ea (diff) | |
download | poseidoc-de896baff7e97fac4dde79078c9a2fa1c652576b.tar.gz poseidoc-de896baff7e97fac4dde79078c9a2fa1c652576b.zip |
Big refactoring
- entities should be more coherent when parsing multiple files
- well defined, language agnostic entity tree
- each module has its own configuration
- less dead code
Diffstat (limited to 'src/parsing/clang/parsing.rs')
-rw-r--r-- | src/parsing/clang/parsing.rs | 748 |
1 files changed, 748 insertions, 0 deletions
diff --git a/src/parsing/clang/parsing.rs b/src/parsing/clang/parsing.rs new file mode 100644 index 0000000..5359253 --- /dev/null +++ b/src/parsing/clang/parsing.rs | |||
@@ -0,0 +1,748 @@ | |||
1 | use super::config::Config; | ||
2 | use super::entities::*; | ||
3 | use crate::types::Entity; | ||
4 | |||
5 | use anyhow::{anyhow, Context, Error, Result}; | ||
6 | use clang::{Clang, CompilationDatabase, Index, TranslationUnit, Usr}; | ||
7 | use codemap::CodeMap; | ||
8 | use thiserror::Error; | ||
9 | |||
10 | use std::collections::BTreeMap; | ||
11 | use std::convert::{TryFrom, TryInto}; | ||
12 | use std::path::{Path, PathBuf}; | ||
13 | |||
14 | #[derive(Debug, Default)] | ||
15 | struct TopLevel { | ||
16 | namespaces: BTreeMap<Usr, Described<Namespace>>, | ||
17 | variables: BTreeMap<Usr, Described<Variable>>, | ||
18 | structs: BTreeMap<Usr, Described<Struct>>, | ||
19 | functions: BTreeMap<Usr, Described<Function>>, | ||
20 | } | ||
21 | |||
22 | /* | ||
23 | enum TopLevelEntry<'a, T> { | ||
24 | Vacant { | ||
25 | parent: &'a mut Described<Namespace>, | ||
26 | }, | ||
27 | /// Vacant, but no semantic parent | ||
28 | TopLevel, | ||
29 | Occupied { | ||
30 | entity: &'a mut Described<T>, | ||
31 | }, | ||
32 | Error, | ||
33 | } | ||
34 | */ | ||
35 | |||
36 | impl TopLevel { | ||
37 | // Somehow has a lifetime issue I can't get my head around | ||
38 | /* | ||
39 | fn entry<'a, T>(&'a mut self, path: &::clang::Entity) -> Result<TopLevelEntry<'a, T>> | ||
40 | where | ||
41 | T: ClangEntity + FromNamespaceParent + FromTopLevel, | ||
42 | { | ||
43 | let usr = path.get_usr().ok_or_else(|| anyhow!("no usr"))?; | ||
44 | if let Some(parent_path) = parent(&path) { | ||
45 | let parent_entry = self.entry::<Namespace>(&parent_path)?; | ||
46 | if let TopLevelEntry::Occupied { | ||
47 | entity: namespace_parent, | ||
48 | } = parent_entry | ||
49 | { | ||
50 | Ok(match T::from_namespace_parent(namespace_parent, &usr) { | ||
51 | None => TopLevelEntry::Vacant { | ||
52 | parent: namespace_parent, | ||
53 | }, | ||
54 | Some(entity) => TopLevelEntry::Occupied { entity }, | ||
55 | }) | ||
56 | } else { | ||
57 | panic!("Wut"); | ||
58 | } | ||
59 | } else { | ||
60 | Ok(match T::from_toplevel(self, &usr) { | ||
61 | Some(entity) => TopLevelEntry::Occupied { entity }, | ||
62 | None => TopLevelEntry::TopLevel, | ||
63 | }) | ||
64 | } | ||
65 | } | ||
66 | */ | ||
67 | |||
68 | fn get_entity_mut(&mut self, path: clang::Entity) -> Option<&mut dyn ClangEntity> { | ||
69 | let usr = path.get_usr()?; | ||
70 | if let Some(parent_path) = parent(path) { | ||
71 | let parent = self.get_entity_mut(parent_path)?; | ||
72 | Some(match path.get_kind().try_into().ok()? { | ||
73 | ClangEntityKind::Namespace => { | ||
74 | &mut parent.get_member_namespaces()?.get_mut(&usr)?.entity | ||
75 | } | ||
76 | ClangEntityKind::Variable(_) => { | ||
77 | &mut parent.get_member_variables()?.get_mut(&usr)?.entity | ||
78 | } | ||
79 | ClangEntityKind::Function(_) => { | ||
80 | &mut parent.get_member_functions()?.get_mut(&usr)?.entity | ||
81 | } | ||
82 | ClangEntityKind::Struct(_) => { | ||
83 | &mut parent.get_member_structs()?.get_mut(&usr)?.entity | ||
84 | } | ||
85 | }) | ||
86 | } else { | ||
87 | Some(match path.get_kind().try_into().ok()? { | ||
88 | ClangEntityKind::Namespace => &mut self.namespaces.get_mut(&usr)?.entity, | ||
89 | ClangEntityKind::Variable(_) => &mut self.variables.get_mut(&usr)?.entity, | ||
90 | ClangEntityKind::Struct(_) => &mut self.structs.get_mut(&usr)?.entity, | ||
91 | ClangEntityKind::Function(_) => &mut self.functions.get_mut(&usr)?.entity, | ||
92 | }) | ||
93 | } | ||
94 | } | ||
95 | |||
96 | fn get_namespace_mut(&mut self, path: clang::Entity) -> Option<&mut Described<Namespace>> { | ||
97 | let usr = path.get_usr()?; | ||
98 | |||
99 | if let Some(parent_path) = parent(path) { | ||
100 | let parent = self.get_entity_mut(parent_path)?; | ||
101 | parent.get_member_namespaces()?.get_mut(&usr) | ||
102 | } else { | ||
103 | self.namespaces.get_mut(&usr) | ||
104 | } | ||
105 | } | ||
106 | |||
107 | fn insert<T>(&mut self, path: clang::Entity, entity: Described<T>) -> Result<()> | ||
108 | where | ||
109 | T: ClangEntity + std::fmt::Debug, | ||
110 | Self: TopLevelManipulation<T>, | ||
111 | Namespace: NamespaceParentManipulation<T>, | ||
112 | { | ||
113 | let usr = path.get_usr().ok_or_else(|| anyhow!("no usr"))?; | ||
114 | if let Some(parent_path) = parent(path) { | ||
115 | if let Some(parent_namespace) = self.get_namespace_mut(parent_path) { | ||
116 | parent_namespace | ||
117 | .entity | ||
118 | .get_members_mut() | ||
119 | // Namespace should be able to contain every kind of entity | ||
120 | .unwrap() | ||
121 | .insert(usr, entity); | ||
122 | Ok(()) | ||
123 | } else { | ||
124 | Err(anyhow!("has parent but no parent in tree")) | ||
125 | } | ||
126 | } else { | ||
127 | self.insert_toplevel(usr, entity); | ||
128 | Ok(()) | ||
129 | } | ||
130 | } | ||
131 | } | ||
132 | |||
133 | // Like .get_semantic_parent(), but return none if the parent is the translation unit | ||
134 | fn parent(libclang_entity: clang::Entity) -> Option<clang::Entity> { | ||
135 | match libclang_entity.get_semantic_parent() { | ||
136 | Some(parent) => { | ||
137 | if parent.get_kind() != clang::EntityKind::TranslationUnit { | ||
138 | Some(parent) | ||
139 | } else { | ||
140 | None | ||
141 | } | ||
142 | } | ||
143 | None => { | ||
144 | warn!("get_semantic_parent() returned None"); | ||
145 | None | ||
146 | } | ||
147 | } | ||
148 | } | ||
149 | |||
150 | trait TopLevelManipulation<T: ClangEntity> { | ||
151 | fn insert_toplevel(&mut self, usr: Usr, entity: Described<T>); | ||
152 | } | ||
153 | |||
154 | impl TopLevelManipulation<Namespace> for TopLevel { | ||
155 | fn insert_toplevel(&mut self, usr: Usr, entity: Described<Namespace>) { | ||
156 | self.namespaces.insert(usr, entity); | ||
157 | } | ||
158 | } | ||
159 | |||
160 | impl TopLevelManipulation<Variable> for TopLevel { | ||
161 | fn insert_toplevel(&mut self, usr: Usr, entity: Described<Variable>) { | ||
162 | self.variables.insert(usr, entity); | ||
163 | } | ||
164 | } | ||
165 | |||
166 | impl TopLevelManipulation<Function> for TopLevel { | ||
167 | fn insert_toplevel(&mut self, usr: Usr, entity: Described<Function>) { | ||
168 | self.functions.insert(usr, entity); | ||
169 | } | ||
170 | } | ||
171 | |||
172 | impl TopLevelManipulation<Struct> for TopLevel { | ||
173 | fn insert_toplevel(&mut self, usr: Usr, entity: Described<Struct>) { | ||
174 | self.structs.insert(usr, entity); | ||
175 | } | ||
176 | } | ||
177 | |||
178 | /* | ||
179 | trait FromTopLevel: ClangEntity + Sized { | ||
180 | fn from_toplevel<'a>(toplevel: &'a mut TopLevel, usr: &Usr) -> Option<&'a mut Described<Self>>; | ||
181 | } | ||
182 | |||
183 | impl FromTopLevel for Namespace { | ||
184 | fn from_toplevel<'a>(toplevel: &'a mut TopLevel, usr: &Usr) -> Option<&'a mut Described<Self>> { | ||
185 | toplevel.namespaces.get_mut(usr) | ||
186 | } | ||
187 | } | ||
188 | |||
189 | impl FromTopLevel for Variable { | ||
190 | fn from_toplevel<'a>(toplevel: &'a mut TopLevel, usr: &Usr) -> Option<&'a mut Described<Self>> { | ||
191 | toplevel.variables.get_mut(usr) | ||
192 | } | ||
193 | } | ||
194 | |||
195 | impl FromTopLevel for Function { | ||
196 | fn from_toplevel<'a>(toplevel: &'a mut TopLevel, usr: &Usr) -> Option<&'a mut Described<Self>> { | ||
197 | toplevel.functions.get_mut(usr) | ||
198 | } | ||
199 | } | ||
200 | |||
201 | impl FromTopLevel for Struct { | ||
202 | fn from_toplevel<'a>(toplevel: &'a mut TopLevel, usr: &Usr) -> Option<&'a mut Described<Self>> { | ||
203 | toplevel.structs.get_mut(usr) | ||
204 | } | ||
205 | } | ||
206 | */ | ||
207 | |||
208 | pub(crate) fn parse_compile_commands( | ||
209 | config: &Config, | ||
210 | codemap: &mut CodeMap, | ||
211 | ) -> Result<BTreeMap<String, Entity>> { | ||
212 | let clang = Clang::new().unwrap(); | ||
213 | let index = Index::new( | ||
214 | &clang, /* exclude from pch = */ false, /* print diagnostics = */ false, | ||
215 | ); | ||
216 | |||
217 | debug!("Extra libclang argument: {:?}", config.extra_args); | ||
218 | |||
219 | debug!( | ||
220 | "Loading compile commands from: {:?}", | ||
221 | config.compile_commands_location | ||
222 | ); | ||
223 | let database = | ||
224 | CompilationDatabase::from_directory(&config.compile_commands_location).map_err(|()| { | ||
225 | CompileCommandsLoadError { | ||
226 | path: config.compile_commands_location.clone(), | ||
227 | } | ||
228 | })?; | ||
229 | |||
230 | let toplevel_directory = std::env::current_dir().context("Cannot read current directory")?; | ||
231 | |||
232 | let mut entities = TopLevel::default(); | ||
233 | |||
234 | for command in database.get_all_compile_commands().get_commands() { | ||
235 | let directory = command.get_directory(); | ||
236 | trace!("Changing directory to: {:?}", directory); | ||
237 | std::env::set_current_dir(&directory) | ||
238 | .with_context(|| format!("Cannot change current directory to: {:?}", directory))?; | ||
239 | |||
240 | let filename = command.get_filename(); | ||
241 | |||
242 | let file_map = codemap.add_file( | ||
243 | filename | ||
244 | .to_str() | ||
245 | .context("File is not valid UTF-8")? | ||
246 | .to_owned(), | ||
247 | std::fs::read_to_string(&filename) | ||
248 | .with_context(|| format!("Cannot readfile: {:?}", filename))?, | ||
249 | ); | ||
250 | |||
251 | trace!("Parsing file: {:?}", filename); | ||
252 | // The file name is passed as an argument in the compile commands | ||
253 | let mut parser = index.parser(""); | ||
254 | parser.skip_function_bodies(true); | ||
255 | |||
256 | let mut clang_arguments = command.get_arguments(); | ||
257 | clang_arguments.extend_from_slice(&config.extra_args); | ||
258 | trace!("Parsing with libclang arguments: {:?}", clang_arguments); | ||
259 | parser.arguments(&clang_arguments); | ||
260 | |||
261 | parse_unit( | ||
262 | &parser | ||
263 | .parse() | ||
264 | .with_context(|| format!("Could not parse file: {:?}", filename))?, | ||
265 | &mut entities, | ||
266 | &toplevel_directory, | ||
267 | file_map.span, | ||
268 | &codemap, | ||
269 | )?; | ||
270 | |||
271 | trace!("Changing directory to: {:?}", directory); | ||
272 | std::env::set_current_dir(&toplevel_directory).with_context(|| { | ||
273 | format!( | ||
274 | "Cannot change current directory to: {:?}", | ||
275 | toplevel_directory | ||
276 | ) | ||
277 | })?; | ||
278 | } | ||
279 | |||
280 | let normalized_entities = entities | ||
281 | .namespaces | ||
282 | .into_iter() | ||
283 | .map(|(usr, entity)| (usr.0, entity.into())) | ||
284 | .chain(entities.variables.into_iter().map(|(usr, entity)| (usr.0, entity.into()))) | ||
285 | .chain(entities.structs.into_iter().map(|(usr, entity)| (usr.0, entity.into()))) | ||
286 | .chain(entities.functions.into_iter().map(|(usr, entity)| (usr.0, entity.into()))) | ||
287 | .collect(); | ||
288 | |||
289 | Ok(normalized_entities) | ||
290 | } | ||
291 | |||
292 | /* | ||
293 | pub(crate) fn parse_file<T, S>(path: T, extra_args: &[S]) -> EntitiesManager | ||
294 | where | ||
295 | T: Into<PathBuf>, | ||
296 | T: AsRef<Path>, | ||
297 | T: ToString, | ||
298 | S: AsRef<str>, | ||
299 | S: std::fmt::Debug, | ||
300 | { | ||
301 | let mut codemap = CodeMap::new(); | ||
302 | let file_map = codemap.add_file(path.to_string(), std::fs::read_to_string(&path).unwrap()); | ||
303 | let file_span = file_map.span; | ||
304 | |||
305 | let clang = Clang::new().unwrap(); | ||
306 | let index = Index::new(&clang, true, false); | ||
307 | let mut parser = index.parser(path); | ||
308 | parser.skip_function_bodies(true); | ||
309 | |||
310 | parser.arguments(&extra_args); | ||
311 | |||
312 | if log_enabled!(log::Level::Debug) { | ||
313 | for extra_arg in extra_args { | ||
314 | debug!("Extra libclang argument: {:?}", extra_arg); | ||
315 | } | ||
316 | } | ||
317 | |||
318 | let trans_unit = parser.parse().unwrap(); | ||
319 | let mut entities = EntitiesManager::new(); | ||
320 | |||
321 | parse_unit( | ||
322 | &trans_unit, | ||
323 | &mut entities, | ||
324 | &std::env::current_dir().unwrap(), | ||
325 | file_span, | ||
326 | &codemap, | ||
327 | ) | ||
328 | .unwrap(); | ||
329 | |||
330 | entities | ||
331 | } | ||
332 | */ | ||
333 | |||
334 | fn parse_unit( | ||
335 | trans_unit: &TranslationUnit, | ||
336 | entities: &mut TopLevel, | ||
337 | base_dir: impl AsRef<Path>, | ||
338 | file_span: codemap::Span, | ||
339 | codemap: &CodeMap, | ||
340 | ) -> Result<()> { | ||
341 | trans_unit.get_entity().visit_children(|entity, _parent| { | ||
342 | if is_in_system_header(entity, &base_dir) { | ||
343 | trace!( | ||
344 | "Entity is in system header, skipping: USR = {:?}", | ||
345 | entity.get_display_name() | ||
346 | ); | ||
347 | return clang::EntityVisitResult::Continue; | ||
348 | } | ||
349 | |||
350 | // TODO: wrap this callback in another function so that we can use the | ||
351 | // "?" operator instead of all these `match`es | ||
352 | let usr = match entity.get_usr() { | ||
353 | Some(usr) => usr, | ||
354 | None => return clang::EntityVisitResult::Continue, | ||
355 | }; | ||
356 | trace!("Entity with USR = {:?}", usr); | ||
357 | debug!("Parsing toplevel entity: {:?}", entity); | ||
358 | |||
359 | add_entity(entity, entities, file_span, codemap) | ||
360 | }); | ||
361 | |||
362 | /* | ||
363 | use codemap_diagnostic::{ColorConfig, Emitter}; | ||
364 | |||
365 | let mut emitter = Emitter::stderr(ColorConfig::Auto, Some(&codemap)); | ||
366 | |||
367 | for diagnostic in trans_unit.get_diagnostics().iter() { | ||
368 | let main_diag = match clang_diag_to_codemap_diag(&diagnostic, file_span) { | ||
369 | Some(diag) => diag, | ||
370 | None => continue, | ||
371 | }; | ||
372 | |||
373 | let sub_diags = diagnostic | ||
374 | .get_children() | ||
375 | .into_iter() | ||
376 | .filter_map(|diagnostic| clang_diag_to_codemap_diag(&diagnostic, file_span)); | ||
377 | |||
378 | let fix_it_diags = diagnostic | ||
379 | .get_fix_its() | ||
380 | .into_iter() | ||
381 | .map(|fix_it| clang_fix_it_to_codemap_diag(&fix_it, file_span)); | ||
382 | |||
383 | emitter.emit( | ||
384 | &std::iter::once(main_diag) | ||
385 | .chain(sub_diags) | ||
386 | .chain(fix_it_diags) | ||
387 | .collect::<Vec<_>>(), | ||
388 | ); | ||
389 | } | ||
390 | */ | ||
391 | |||
392 | Ok(()) | ||
393 | } | ||
394 | |||
395 | fn is_in_system_header(entity: clang::Entity, base_dir: impl AsRef<Path>) -> bool { | ||
396 | if entity.is_in_system_header() { | ||
397 | true | ||
398 | } else if let Some(location) = entity.get_location() { | ||
399 | if let Some(file) = location.get_file_location().file { | ||
400 | !file | ||
401 | .get_path() | ||
402 | .canonicalize() | ||
403 | .unwrap() | ||
404 | .starts_with(base_dir) | ||
405 | } else { | ||
406 | // Not defined in a file? probably shouldn't document | ||
407 | true | ||
408 | } | ||
409 | } else { | ||
410 | // Not defined anywhere? probably shouldn't document | ||
411 | true | ||
412 | } | ||
413 | } | ||
414 | |||
415 | // Entries encountered in the toplevel lexical context | ||
416 | fn add_entity( | ||
417 | libclang_entity: clang::Entity, | ||
418 | toplevel: &mut TopLevel, | ||
419 | file_span: codemap::Span, | ||
420 | codemap: &CodeMap, | ||
421 | ) -> clang::EntityVisitResult { | ||
422 | if libclang_entity.get_usr().is_none() { | ||
423 | return clang::EntityVisitResult::Continue; | ||
424 | }; | ||
425 | |||
426 | let kind = match ClangEntityKind::try_from(libclang_entity.get_kind()) { | ||
427 | Ok(kind) => kind, | ||
428 | Err(err) => { | ||
429 | use codemap_diagnostic::{ | ||
430 | ColorConfig, Diagnostic, Emitter, Level, SpanLabel, SpanStyle, | ||
431 | }; | ||
432 | let spans = if let Some(range) = libclang_entity.get_range() { | ||
433 | // TODO: add every file parsed in this translation unit to the | ||
434 | // codemap, so we can properly report errors | ||
435 | if !range.is_in_main_file() { | ||
436 | vec![] | ||
437 | } else { | ||
438 | let begin = range.get_start().get_file_location().offset as u64; | ||
439 | let end = range.get_end().get_file_location().offset as u64; | ||
440 | |||
441 | vec![SpanLabel { | ||
442 | span: file_span.subspan(begin, end), | ||
443 | label: None, | ||
444 | style: SpanStyle::Primary, | ||
445 | }] | ||
446 | } | ||
447 | } else { | ||
448 | vec![] | ||
449 | }; | ||
450 | |||
451 | let diag = Diagnostic { | ||
452 | level: Level::Warning, | ||
453 | message: format!("{}", err), | ||
454 | code: None, | ||
455 | spans, | ||
456 | }; | ||
457 | |||
458 | let mut emitter = Emitter::stderr(ColorConfig::Auto, Some(codemap)); | ||
459 | emitter.emit(&[diag]); | ||
460 | |||
461 | return clang::EntityVisitResult::Continue; | ||
462 | } | ||
463 | }; | ||
464 | |||
465 | if let Some(in_tree_entity) = toplevel.get_entity_mut(libclang_entity) { | ||
466 | // if current.has_documentation && !tree.has_documentation { | ||
467 | // append_documentation | ||
468 | // } | ||
469 | } else if libclang_entity.is_definition() { | ||
470 | // TODO: This probably means that you can't put documentation in forward declarations. | ||
471 | // | ||
472 | // This seems restrictive, but since there can be multiple declarations but only one definition, | ||
473 | // you should probably put your documentation on the definition anyway? | ||
474 | // | ||
475 | // Also, skipping forward declarations allows us to not have to insert, then update the tree | ||
476 | // when we see the definition. | ||
477 | |||
478 | let result = match kind { | ||
479 | ClangEntityKind::Namespace => Described::<Namespace>::try_from(libclang_entity) | ||
480 | .and_then(|namespace| toplevel.insert(libclang_entity, namespace)), | ||
481 | ClangEntityKind::Variable(_) => Described::<Variable>::try_from(libclang_entity) | ||
482 | .and_then(|variable| toplevel.insert(libclang_entity, variable)), | ||
483 | ClangEntityKind::Struct(_) => Described::<Struct>::try_from(libclang_entity) | ||
484 | .and_then(|r#struct| toplevel.insert(libclang_entity, r#struct)), | ||
485 | ClangEntityKind::Function(_) => Described::<Function>::try_from(libclang_entity) | ||
486 | .and_then(|function| toplevel.insert(libclang_entity, function)), | ||
487 | }; | ||
488 | // TODO: check result | ||
489 | } | ||
490 | |||
491 | if kind == ClangEntityKind::Namespace { | ||
492 | // Recurse here since namespace definitions are allowed to change between translation units. | ||
493 | ::clang::EntityVisitResult::Recurse | ||
494 | } else { | ||
495 | ::clang::EntityVisitResult::Continue | ||
496 | } | ||
497 | } | ||
498 | |||
499 | impl<'a, T> TryFrom<clang::Entity<'a>> for Described<T> | ||
500 | where | ||
501 | T: TryFrom<clang::Entity<'a>, Error = Error>, | ||
502 | { | ||
503 | type Error = Error; | ||
504 | |||
505 | fn try_from(entity: clang::Entity<'a>) -> Result<Self, Error> { | ||
506 | Ok(Described::<T> { | ||
507 | description: get_description(entity)?, | ||
508 | entity: T::try_from(entity)?, | ||
509 | }) | ||
510 | } | ||
511 | } | ||
512 | |||
513 | impl<'a> TryFrom<clang::Entity<'a>> for Namespace { | ||
514 | type Error = Error; | ||
515 | |||
516 | fn try_from(entity: clang::Entity) -> Result<Self, Error> { | ||
517 | match entity.get_kind().try_into() { | ||
518 | Ok(ClangEntityKind::Namespace) => {} | ||
519 | _ => panic!("Trying to parse a non-variable into a variable"), | ||
520 | } | ||
521 | debug!("Parsing Namespace: {:?}", entity); | ||
522 | |||
523 | // Do not recurse here, but recurse in the main loop, since namespace | ||
524 | // definitions is allowed to change between translation units | ||
525 | |||
526 | Ok(Namespace { | ||
527 | member_namespaces: Default::default(), | ||
528 | member_variables: Default::default(), | ||
529 | member_structs: Default::default(), | ||
530 | member_functions: Default::default(), | ||
531 | }) | ||
532 | } | ||
533 | } | ||
534 | |||
535 | impl<'a> TryFrom<clang::Entity<'a>> for Variable { | ||
536 | type Error = Error; | ||
537 | |||
538 | fn try_from(entity: clang::Entity) -> Result<Self, Error> { | ||
539 | let variable_kind; | ||
540 | match entity.get_kind().try_into() { | ||
541 | Ok(ClangEntityKind::Variable(kind)) => { | ||
542 | variable_kind = kind; | ||
543 | } | ||
544 | _ => panic!("Trying to parse a non-variable into a variable"), | ||
545 | } | ||
546 | debug!("Parsing Variable: {:?}", entity); | ||
547 | |||
548 | let r#type = entity.get_type().unwrap().get_display_name(); | ||
549 | trace!("Variable has type: {:?}", r#type); | ||
550 | |||
551 | Ok(Variable { | ||
552 | r#type, | ||
553 | kind: variable_kind, | ||
554 | }) | ||
555 | } | ||
556 | } | ||
557 | |||
558 | impl<'a> TryFrom<clang::Entity<'a>> for Struct { | ||
559 | type Error = Error; | ||
560 | |||
561 | fn try_from(entity: clang::Entity) -> Result<Self, Error> { | ||
562 | let struct_kind; | ||
563 | match entity.get_kind().try_into() { | ||
564 | Ok(ClangEntityKind::Struct(kind)) => { | ||
565 | struct_kind = kind; | ||
566 | } | ||
567 | _ => panic!("Trying to parse a non-class into a class"), | ||
568 | } | ||
569 | debug!("Parsing Struct: {:?}", entity); | ||
570 | |||
571 | let mut member_variables = BTreeMap::new(); | ||
572 | let mut member_structs = BTreeMap::new(); | ||
573 | let mut member_functions = BTreeMap::new(); | ||
574 | |||
575 | for child in entity.get_children() { | ||
576 | trace!("Struct has child: {:?}", child); | ||
577 | |||
578 | match child.get_kind().try_into() { | ||
579 | Ok(ClangEntityKind::Variable(_)) => { | ||
580 | let child_usr = child.get_usr().ok_or_else(|| anyhow!("no usr"))?; | ||
581 | member_variables.insert(child_usr, Described::<Variable>::try_from(child)?); | ||
582 | } | ||
583 | Ok(ClangEntityKind::Struct(_)) => { | ||
584 | let child_usr: Usr = child.get_usr().ok_or_else(|| anyhow!("no usr"))?; | ||
585 | member_structs.insert(child_usr, Described::<Struct>::try_from(child)?); | ||
586 | } | ||
587 | Ok(ClangEntityKind::Function(_)) => { | ||
588 | let child_usr = child.get_usr().ok_or_else(|| anyhow!("no usr"))?; | ||
589 | member_functions.insert(child_usr, Described::<Function>::try_from(child)?); | ||
590 | } | ||
591 | _ => trace!("Skipping child"), | ||
592 | } | ||
593 | } | ||
594 | |||
595 | Ok(Struct { | ||
596 | kind: struct_kind, | ||
597 | member_functions, | ||
598 | member_structs, | ||
599 | member_variables, | ||
600 | }) | ||
601 | } | ||
602 | } | ||
603 | |||
604 | impl<'a> TryFrom<clang::Entity<'a>> for Function { | ||
605 | type Error = Error; | ||
606 | |||
607 | fn try_from(entity: clang::Entity) -> Result<Self, Error> { | ||
608 | let function_kind; | ||
609 | match entity.get_kind().try_into() { | ||
610 | Ok(ClangEntityKind::Function(kind)) => { | ||
611 | function_kind = kind; | ||
612 | } | ||
613 | _ => panic!("Trying to parse a non-function into a function"), | ||
614 | } | ||
615 | debug!("Parsing Function: {:?}", entity); | ||
616 | |||
617 | let return_type = entity.get_result_type().unwrap().get_display_name(); | ||
618 | trace!("Function has return type: {:?}", return_type); | ||
619 | let arguments = entity | ||
620 | .get_arguments() | ||
621 | .unwrap() | ||
622 | .into_iter() | ||
623 | .map(|arg| { | ||
624 | let name = arg | ||
625 | .get_display_name() | ||
626 | .unwrap_or_else(|| String::from("unnamed")); | ||
627 | let r#type = arg.get_type().unwrap().get_display_name(); | ||
628 | trace!("Function has argument {:?} of type {:?}", name, r#type); | ||
629 | FunctionArgument { name, r#type } | ||
630 | }) | ||
631 | .collect(); | ||
632 | |||
633 | Ok(Function { | ||
634 | kind: function_kind, | ||
635 | arguments, | ||
636 | return_type, | ||
637 | }) | ||
638 | } | ||
639 | } | ||
640 | |||
641 | fn get_description(entity: clang::Entity) -> Result<Description> { | ||
642 | let name = entity | ||
643 | .get_display_name() | ||
644 | .ok_or_else(|| anyhow!("Entity has no name: {:?}", entity))?; | ||
645 | |||
646 | // TODO: is that the best? | ||
647 | if let (Some(brief), Some(comment)) = (entity.get_comment_brief(), entity.get_comment()) { | ||
648 | Ok(Description { | ||
649 | name, | ||
650 | brief, | ||
651 | detailed: parse_comment(comment), | ||
652 | }) | ||
653 | } else { | ||
654 | Ok(Description { | ||
655 | name, | ||
656 | brief: String::new(), | ||
657 | detailed: String::new(), | ||
658 | }) | ||
659 | } | ||
660 | } | ||
661 | |||
662 | pub fn parse_comment(raw: String) -> String { | ||
663 | #[derive(Debug)] | ||
664 | enum CommentStyle { | ||
665 | // Comments of type `/**` or `/*!` | ||
666 | Starred, | ||
667 | // Comments of type `///` | ||
668 | SingleLine, | ||
669 | } | ||
670 | |||
671 | let mut chars = raw.chars(); | ||
672 | let style = match &chars.as_str()[..3] { | ||
673 | "/*!" | "/**" => CommentStyle::Starred, | ||
674 | "///" => CommentStyle::SingleLine, | ||
675 | _ => panic!("Comment is empty or doesn't start with either `///`, `/**`, or `/*!`"), | ||
676 | }; | ||
677 | |||
678 | chars.nth(2); | ||
679 | |||
680 | let mut result = String::new(); | ||
681 | |||
682 | 'parse_loop: loop { | ||
683 | let maybe_space = chars.next(); | ||
684 | let mut empty_line = false; | ||
685 | match maybe_space { | ||
686 | // TODO: Warn on empty comments | ||
687 | None => break, | ||
688 | Some(' ') => {} | ||
689 | Some('\n') => { | ||
690 | empty_line = true; | ||
691 | result.push('\n'); | ||
692 | } | ||
693 | Some(ch) => result.push(ch), | ||
694 | } | ||
695 | |||
696 | if !empty_line { | ||
697 | let rest = chars.as_str(); | ||
698 | match rest.find('\n') { | ||
699 | None => { | ||
700 | result.push_str(rest); | ||
701 | break; | ||
702 | } | ||
703 | Some(position) => { | ||
704 | result.push_str(&rest[..=position]); | ||
705 | chars.nth(position); | ||
706 | } | ||
707 | } | ||
708 | } | ||
709 | |||
710 | // Beginning of the line | ||
711 | let first_non_ws_ch = 'ws_loop: loop { | ||
712 | let maybe_whitespace = chars.next(); | ||
713 | match maybe_whitespace { | ||
714 | None => break 'parse_loop, | ||
715 | Some(ch) if ch.is_whitespace() => continue, | ||
716 | Some(ch) => break 'ws_loop ch, | ||
717 | } | ||
718 | }; | ||
719 | |||
720 | match style { | ||
721 | CommentStyle::Starred if first_non_ws_ch == '*' => { | ||
722 | if &chars.as_str()[..1] == "/" { | ||
723 | break; | ||
724 | } | ||
725 | } | ||
726 | CommentStyle::Starred => result.push(first_non_ws_ch), | ||
727 | CommentStyle::SingleLine => { | ||
728 | assert!(first_non_ws_ch == '/'); | ||
729 | let rest = chars.as_str(); | ||
730 | if &rest[..2] == "//" { | ||
731 | chars.nth(1); | ||
732 | } else if &rest[..1] == "/" { | ||
733 | chars.nth(0); | ||
734 | } else { | ||
735 | panic!("Could not parse comment"); | ||
736 | } | ||
737 | } | ||
738 | } | ||
739 | } | ||
740 | |||
741 | result | ||
742 | } | ||
743 | |||
744 | #[derive(Debug, Clone, Error)] | ||
745 | #[error("Failed to load 'compile_commands.json' at path: {:?}", path)] | ||
746 | pub(crate) struct CompileCommandsLoadError { | ||
747 | path: PathBuf, | ||
748 | } | ||