summaryrefslogtreecommitdiffstats
path: root/src/parsing
diff options
context:
space:
mode:
authorMinijackson <minijackson@riseup.net>2019-12-18 20:56:53 +0100
committerMinijackson <minijackson@riseup.net>2019-12-18 20:56:53 +0100
commitde896baff7e97fac4dde79078c9a2fa1c652576b (patch)
tree512b67b91d64e51d63f7ac5ff925a5c96d9aaf3c /src/parsing
parent860b73f1644ecd6548ae403ec483625fb7b625ea (diff)
downloadposeidoc-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')
-rw-r--r--src/parsing/clang/config.rs53
-rw-r--r--src/parsing/clang/entities.rs369
-rw-r--r--src/parsing/clang/mod.rs5
-rw-r--r--src/parsing/clang/parsing.rs748
-rw-r--r--src/parsing/mod.rs1
5 files changed, 1176 insertions, 0 deletions
diff --git a/src/parsing/clang/config.rs b/src/parsing/clang/config.rs
new file mode 100644
index 0000000..cb05876
--- /dev/null
+++ b/src/parsing/clang/config.rs
@@ -0,0 +1,53 @@
1use anyhow::Result;
2use serde_derive::{Deserialize, Serialize};
3use structopt::StructOpt;
4
5use std::path::PathBuf;
6
7#[derive(Debug, Clone, StructOpt, Deserialize, Serialize)]
8#[serde(rename_all = "kebab-case")]
9pub(crate) struct Config {
10 pub(crate) compile_commands_location: PathBuf,
11 pub(crate) extra_args: Vec<String>,
12}
13
14#[derive(Debug, Clone, Default, StructOpt, Deserialize, Serialize)]
15#[serde(rename_all = "kebab-case")]
16pub(crate) struct ProvidedConfig {
17 #[structopt(long = "clang-compile-commands-location")]
18 pub(crate) compile_commands_location: Option<PathBuf>,
19 #[structopt(long = "clang-extra-args", number_of_values = 1)]
20 #[serde(default)]
21 pub(crate) extra_args: Vec<String>,
22}
23
24impl Default for Config {
25 fn default() -> Self {
26 Config::from_merge(
27 ProvidedConfig::default(),
28 ProvidedConfig::default(),
29 )
30 // Currently errors out only on CLI parse fail with clang's extra args
31 .unwrap()
32 }
33}
34
35impl Config {
36 pub(crate) fn from_merge(
37 cli: ProvidedConfig,
38 mut config: ProvidedConfig,
39 ) -> Result<Self> {
40 let mut extra_args = Vec::new();
41 for args in cli.extra_args {
42 extra_args.append(&mut ::shell_words::split(&args)?);
43 }
44 extra_args.append(&mut config.extra_args);
45 Ok(Self {
46 compile_commands_location: cli
47 .compile_commands_location
48 .or(config.compile_commands_location)
49 .unwrap_or_else(|| PathBuf::from(r".")),
50 extra_args,
51 })
52 }
53}
diff --git a/src/parsing/clang/entities.rs b/src/parsing/clang/entities.rs
new file mode 100644
index 0000000..a8062bb
--- /dev/null
+++ b/src/parsing/clang/entities.rs
@@ -0,0 +1,369 @@
1use crate::types::Entity;
2
3use clang::{EntityKind, Usr};
4use thiserror::Error;
5
6use std::collections::BTreeMap;
7use std::convert::TryFrom;
8
9// TODO: factor out hardcoded strings
10
11#[derive(Debug)]
12pub(super) struct Children {
13 namespaces: Option<BTreeMap<Usr, Described<Namespace>>>,
14 variables: Option<BTreeMap<Usr, Described<Variable>>>,
15 structs: Option<BTreeMap<Usr, Described<Struct>>>,
16 functions: Option<BTreeMap<Usr, Described<Function>>>,
17}
18
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub(super) enum ClangEntityKind {
21 Namespace,
22 Variable(VariableKind),
23 Struct(StructKind),
24 Function(FunctionKind),
25}
26
27#[derive(Debug, Clone, PartialEq, Eq)]
28pub(super) enum VariableKind {
29 Variable,
30 Field,
31}
32
33#[derive(Debug, Clone, PartialEq, Eq)]
34pub(super) enum StructKind {
35 Struct,
36 Class,
37}
38
39#[derive(Debug, Clone, PartialEq, Eq)]
40pub(super) enum FunctionKind {
41 Function,
42 Method,
43}
44
45#[derive(Debug, Error)]
46#[error("Unsupported Clang entity kind: {:?}", _0)]
47pub(super) struct TryFromEntityKindError(String);
48
49impl TryFrom<EntityKind> for ClangEntityKind {
50 type Error = TryFromEntityKindError;
51
52 fn try_from(kind: EntityKind) -> Result<ClangEntityKind, TryFromEntityKindError> {
53 Ok(match kind {
54 EntityKind::Namespace => ClangEntityKind::Namespace,
55 EntityKind::VarDecl => ClangEntityKind::Variable(VariableKind::Variable),
56 EntityKind::FieldDecl => ClangEntityKind::Variable(VariableKind::Field),
57 EntityKind::FunctionDecl => ClangEntityKind::Function(FunctionKind::Function),
58 EntityKind::Method | EntityKind::Constructor => {
59 ClangEntityKind::Function(FunctionKind::Method)
60 }
61 EntityKind::StructDecl => ClangEntityKind::Struct(StructKind::Struct),
62 EntityKind::ClassDecl => ClangEntityKind::Struct(StructKind::Class),
63 kind => return Err(TryFromEntityKindError(format!("{:?}", kind))),
64 })
65 }
66}
67
68pub(super) trait ClangEntity {
69 fn kind(&self) -> &'static str;
70
71 fn get_member_namespaces(&mut self) -> Option<&mut BTreeMap<Usr, Described<Namespace>>> {
72 None
73 }
74 fn get_member_variables(&mut self) -> Option<&mut BTreeMap<Usr, Described<Variable>>> {
75 None
76 }
77 fn get_member_structs(&mut self) -> Option<&mut BTreeMap<Usr, Described<Struct>>> {
78 None
79 }
80 fn get_member_functions(&mut self) -> Option<&mut BTreeMap<Usr, Described<Function>>> {
81 None
82 }
83
84 fn into_children(self) -> Children
85 where
86 Self: Sized,
87 {
88 Children {
89 namespaces: None,
90 variables: None,
91 structs: None,
92 functions: None,
93 }
94 }
95}
96
97/*
98pub(super) trait FromNamespaceParent: ClangEntity + Sized {
99 fn from_namespace_parent<'a>(
100 parent: &'a mut Described<Namespace>,
101 name: &Usr,
102 ) -> Option<&'a mut Described<Self>>;
103}
104*/
105
106pub(super) trait NamespaceParentManipulation<T: ClangEntity + Sized> {
107 fn get_members_mut(&mut self) -> Option<&mut BTreeMap<Usr, Described<T>>>;
108}
109
110impl<U> NamespaceParentManipulation<Namespace> for U
111where
112 U: ClangEntity,
113{
114 fn get_members_mut(&mut self) -> Option<&mut BTreeMap<Usr, Described<Namespace>>> {
115 self.get_member_namespaces()
116 }
117}
118
119impl<U> NamespaceParentManipulation<Variable> for U
120where
121 U: ClangEntity,
122{
123 fn get_members_mut(&mut self) -> Option<&mut BTreeMap<Usr, Described<Variable>>> {
124 self.get_member_variables()
125 }
126}
127
128impl<U> NamespaceParentManipulation<Function> for U
129where
130 U: ClangEntity,
131{
132 fn get_members_mut(&mut self) -> Option<&mut BTreeMap<Usr, Described<Function>>> {
133 self.get_member_functions()
134 }
135}
136
137impl<U> NamespaceParentManipulation<Struct> for U
138where
139 U: ClangEntity,
140{
141 fn get_members_mut(&mut self) -> Option<&mut BTreeMap<Usr, Described<Struct>>> {
142 self.get_member_structs()
143 }
144}
145
146impl<T: ClangEntity> From<Described<T>> for Entity {
147 fn from(entity: Described<T>) -> Self {
148 let mut children = BTreeMap::new();
149
150 let kind = String::from(entity.entity.kind());
151 let clang_children = entity.entity.into_children();
152
153 if let Some(namespaces) = clang_children.namespaces {
154 children.insert(
155 String::from("namespace"),
156 namespaces
157 .into_iter()
158 .map(|(usr, namespace)| (usr.0, namespace.into()))
159 .collect(),
160 );
161 }
162
163 if let Some(variables) = clang_children.variables {
164 children.insert(
165 String::from("variable"),
166 variables
167 .into_iter()
168 .map(|(usr, variable)| (usr.0, variable.into()))
169 .collect(),
170 );
171 }
172
173 if let Some(structs) = clang_children.structs {
174 children.insert(
175 String::from("struct"),
176 structs
177 .into_iter()
178 .map(|(usr, the_struct)| (usr.0, the_struct.into()))
179 .collect(),
180 );
181 }
182
183 if let Some(functions) = clang_children.functions {
184 children.insert(
185 String::from("function"),
186 functions
187 .into_iter()
188 .map(|(usr, function)| (usr.0, function.into()))
189 .collect(),
190 );
191 }
192
193 Entity {
194 name: entity.description.name,
195 language: String::from("clang"),
196 kind,
197 brief_description: entity.description.brief,
198 documentation: entity.description.detailed,
199 children,
200 }
201 }
202}
203
204#[derive(Debug, Clone, Default)]
205pub(super) struct Description {
206 pub(super) name: String,
207 pub(super) brief: String,
208 pub(super) detailed: String,
209}
210
211#[derive(Debug, Clone)]
212pub(super) struct Described<T> {
213 pub(super) description: Description,
214 pub(super) entity: T,
215}
216
217#[derive(Debug, Clone)]
218pub(super) struct Namespace {
219 pub(super) member_namespaces: BTreeMap<Usr, Described<Namespace>>,
220 pub(super) member_variables: BTreeMap<Usr, Described<Variable>>,
221 pub(super) member_structs: BTreeMap<Usr, Described<Struct>>,
222 pub(super) member_functions: BTreeMap<Usr, Described<Function>>,
223}
224
225impl ClangEntity for Namespace {
226 fn kind(&self) -> &'static str {
227 "namespace"
228 }
229
230 fn get_member_namespaces(&mut self) -> Option<&mut BTreeMap<Usr, Described<Namespace>>> {
231 Some(&mut self.member_namespaces)
232 }
233 fn get_member_variables(&mut self) -> Option<&mut BTreeMap<Usr, Described<Variable>>> {
234 Some(&mut self.member_variables)
235 }
236 fn get_member_structs(&mut self) -> Option<&mut BTreeMap<Usr, Described<Struct>>> {
237 Some(&mut self.member_structs)
238 }
239 fn get_member_functions(&mut self) -> Option<&mut BTreeMap<Usr, Described<Function>>> {
240 Some(&mut self.member_functions)
241 }
242
243 fn into_children(self) -> Children {
244 Children {
245 namespaces: Some(self.member_namespaces),
246 variables: Some(self.member_variables),
247 structs: Some(self.member_structs),
248 functions: Some(self.member_functions),
249 }
250 }
251}
252
253/*
254impl FromNamespaceParent for Namespace {
255 fn from_namespace_parent<'a>(
256 parent: &'a mut Described<Namespace>,
257 name: &Usr,
258 ) -> Option<&'a mut Described<Self>> {
259 parent.entity.member_namespaces.get_mut(name)
260 }
261}
262*/
263
264#[derive(Debug, Clone)]
265pub(super) struct Variable {
266 pub(super) kind: VariableKind,
267 pub(super) r#type: String,
268}
269
270impl ClangEntity for Variable {
271 fn kind(&self) -> &'static str {
272 match self.kind {
273 VariableKind::Variable => "variable",
274 VariableKind::Field => "field",
275 }
276 }
277}
278
279/*
280impl FromNamespaceParent for Variable {
281 fn from_namespace_parent<'a>(
282 parent: &'a mut Described<Namespace>,
283 name: &Usr,
284 ) -> Option<&'a mut Described<Self>> {
285 parent.entity.member_variables.get_mut(name)
286 }
287}
288*/
289
290#[derive(Debug, Clone)]
291pub(super) struct Struct {
292 //pub(super) member_types: Vec<Usr>,
293 pub(super) kind: StructKind,
294 pub(super) member_variables: BTreeMap<Usr, Described<Variable>>,
295 pub(super) member_structs: BTreeMap<Usr, Described<Struct>>,
296 pub(super) member_functions: BTreeMap<Usr, Described<Function>>,
297}
298
299impl ClangEntity for Struct {
300 fn kind(&self) -> &'static str {
301 match self.kind {
302 StructKind::Struct => "struct",
303 StructKind::Class => "class",
304 }
305 }
306
307 fn get_member_variables(&mut self) -> Option<&mut BTreeMap<Usr, Described<Variable>>> {
308 Some(&mut self.member_variables)
309 }
310 fn get_member_structs(&mut self) -> Option<&mut BTreeMap<Usr, Described<Struct>>> {
311 Some(&mut self.member_structs)
312 }
313 fn get_member_functions(&mut self) -> Option<&mut BTreeMap<Usr, Described<Function>>> {
314 Some(&mut self.member_functions)
315 }
316
317 fn into_children(self) -> Children {
318 Children {
319 namespaces: None,
320 variables: Some(self.member_variables),
321 structs: Some(self.member_structs),
322 functions: Some(self.member_functions),
323 }
324 }
325}
326
327/*
328impl FromNamespaceParent for Struct {
329 fn from_namespace_parent<'a>(
330 parent: &'a mut Described<Namespace>,
331 name: &Usr,
332 ) -> Option<&'a mut Described<Self>> {
333 parent.entity.member_structs.get_mut(name)
334 }
335}
336*/
337
338#[derive(Debug, Clone)]
339pub(super) struct Function {
340 pub(super) kind: FunctionKind,
341 pub(super) arguments: Vec<FunctionArgument>,
342 pub(super) return_type: String,
343}
344
345impl ClangEntity for Function {
346 fn kind(&self) -> &'static str {
347 match self.kind {
348 FunctionKind::Function => "function",
349 FunctionKind::Method => "method",
350 }
351 }
352}
353
354/*
355impl FromNamespaceParent for Function {
356 fn from_namespace_parent<'a>(
357 parent: &'a mut Described<Namespace>,
358 name: &Usr,
359 ) -> Option<&'a mut Described<Self>> {
360 parent.entity.member_functions.get_mut(name)
361 }
362}
363*/
364
365#[derive(Debug, Clone)]
366pub(super) struct FunctionArgument {
367 pub(super) name: String,
368 pub(super) r#type: String,
369}
diff --git a/src/parsing/clang/mod.rs b/src/parsing/clang/mod.rs
new file mode 100644
index 0000000..047c49e
--- /dev/null
+++ b/src/parsing/clang/mod.rs
@@ -0,0 +1,5 @@
1pub(crate) mod config;
2mod entities;
3mod parsing;
4
5pub(crate) use parsing::parse_compile_commands;
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 @@
1use super::config::Config;
2use super::entities::*;
3use crate::types::Entity;
4
5use anyhow::{anyhow, Context, Error, Result};
6use clang::{Clang, CompilationDatabase, Index, TranslationUnit, Usr};
7use codemap::CodeMap;
8use thiserror::Error;
9
10use std::collections::BTreeMap;
11use std::convert::{TryFrom, TryInto};
12use std::path::{Path, PathBuf};
13
14#[derive(Debug, Default)]
15struct 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/*
23enum 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
36impl 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
134fn 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
150trait TopLevelManipulation<T: ClangEntity> {
151 fn insert_toplevel(&mut self, usr: Usr, entity: Described<T>);
152}
153
154impl TopLevelManipulation<Namespace> for TopLevel {
155 fn insert_toplevel(&mut self, usr: Usr, entity: Described<Namespace>) {
156 self.namespaces.insert(usr, entity);
157 }
158}
159
160impl TopLevelManipulation<Variable> for TopLevel {
161 fn insert_toplevel(&mut self, usr: Usr, entity: Described<Variable>) {
162 self.variables.insert(usr, entity);
163 }
164}
165
166impl TopLevelManipulation<Function> for TopLevel {
167 fn insert_toplevel(&mut self, usr: Usr, entity: Described<Function>) {
168 self.functions.insert(usr, entity);
169 }
170}
171
172impl 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/*
179trait FromTopLevel: ClangEntity + Sized {
180 fn from_toplevel<'a>(toplevel: &'a mut TopLevel, usr: &Usr) -> Option<&'a mut Described<Self>>;
181}
182
183impl 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
189impl 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
195impl 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
201impl 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
208pub(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/*
293pub(crate) fn parse_file<T, S>(path: T, extra_args: &[S]) -> EntitiesManager
294where
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
334fn 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
395fn 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
416fn 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
499impl<'a, T> TryFrom<clang::Entity<'a>> for Described<T>
500where
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
513impl<'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
535impl<'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
558impl<'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
604impl<'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
641fn 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
662pub 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)]
746pub(crate) struct CompileCommandsLoadError {
747 path: PathBuf,
748}
diff --git a/src/parsing/mod.rs b/src/parsing/mod.rs
new file mode 100644
index 0000000..0736eee
--- /dev/null
+++ b/src/parsing/mod.rs
@@ -0,0 +1 @@
pub(crate) mod clang;