summaryrefslogtreecommitdiffstats
path: root/src/parsing.rs
diff options
context:
space:
mode:
authorMinijackson <minijackson@riseup.net>2019-09-08 16:15:46 +0200
committerMinijackson <minijackson@riseup.net>2019-11-10 16:37:59 +0100
commit3301430c676e4af6b95d96b6408a66f9d2768653 (patch)
tree12810ce81a3b1d3cb23270fc5119016d5f6c325a /src/parsing.rs
downloadposeidoc-3301430c676e4af6b95d96b6408a66f9d2768653.tar.gz
poseidoc-3301430c676e4af6b95d96b6408a66f9d2768653.zip
First version
Diffstat (limited to 'src/parsing.rs')
-rw-r--r--src/parsing.rs457
1 files changed, 457 insertions, 0 deletions
diff --git a/src/parsing.rs b/src/parsing.rs
new file mode 100644
index 0000000..137b89d
--- /dev/null
+++ b/src/parsing.rs
@@ -0,0 +1,457 @@
1use crate::entities::*;
2
3use clang::{Clang, Index};
4use codemap::CodeMap;
5
6use std::collections::HashMap;
7use std::path::{Path, PathBuf};
8
9pub(crate) fn parse_file<T>(path: T, mut extra_args: Vec<&str>) -> EntitiesManager
10where
11 T: Into<PathBuf>,
12 T: AsRef<Path>,
13 T: ToString,
14{
15 let mut codemap = CodeMap::new();
16 let file_map = codemap.add_file(path.to_string(), std::fs::read_to_string(&path).unwrap());
17 let file_span = file_map.span;
18
19 let clang = Clang::new().unwrap();
20 let index = Index::new(&clang, true, false);
21 let mut parser = index.parser(path);
22 parser.skip_function_bodies(true);
23
24 extra_args.push("-Werror=documentation");
25 parser.arguments(&extra_args);
26
27 if log_enabled!(log::Level::Debug) {
28 for extra_arg in extra_args {
29 debug!("Extra LibClang argument: {:?}", extra_arg);
30 }
31 }
32
33 let trans_unit = parser.parse().unwrap();
34
35 let mut manager = EntitiesManager::new();
36
37 trans_unit.get_entity().visit_children(|entity, _parent| {
38 if entity.is_in_system_header() {
39 trace!(
40 "Entity is in system header, skipping: USR = {:?}",
41 entity.get_display_name()
42 );
43 return clang::EntityVisitResult::Continue;
44 }
45
46 // TODO: wrap this callback in another function so that we can use the
47 // "?" operator instead of all these `match`es
48 let usr = match entity.get_usr() {
49 Some(usr) => usr,
50 None => return clang::EntityVisitResult::Continue,
51 };
52 trace!("Entity with USR = {:?}", usr);
53
54 let name = match entity.get_name() {
55 Some(name) => name,
56 None => return clang::EntityVisitResult::Continue,
57 };
58
59 debug!("Parsing toplevel entity with name = {:?}", name);
60
61 let entity = entity.into_poseidoc_entity(&mut manager);
62
63 manager.insert_toplevel(usr.into(), entity);
64
65 clang::EntityVisitResult::Continue
66 });
67
68 use codemap_diagnostic::{ColorConfig, Emitter};
69
70 let mut emitter = Emitter::stderr(ColorConfig::Auto, Some(&codemap));
71
72 for diagnostic in trans_unit.get_diagnostics().iter() {
73 let main_diag = match clang_diag_to_codemap_diag(&diagnostic, &file_span) {
74 Some(diag) => diag,
75 None => continue,
76 };
77
78 let sub_diags = diagnostic
79 .get_children()
80 .into_iter()
81 .filter_map(|diagnostic| clang_diag_to_codemap_diag(&diagnostic, &file_span));
82
83 let fix_it_diags = diagnostic
84 .get_fix_its()
85 .into_iter()
86 .map(|fix_it| clang_fix_it_to_codemap_diag(&fix_it, &file_span));
87
88 emitter.emit(
89 &std::iter::once(main_diag)
90 .chain(sub_diags)
91 .chain(fix_it_diags)
92 .collect::<Vec<_>>(),
93 );
94 }
95
96 manager
97}
98
99fn clang_diag_to_codemap_diag(
100 diagnostic: &clang::diagnostic::Diagnostic,
101 file_span: &codemap::Span,
102) -> Option<codemap_diagnostic::Diagnostic> {
103 use codemap_diagnostic::{Diagnostic, Level, SpanLabel, SpanStyle};
104
105 let ranges = diagnostic.get_ranges();
106
107 let (begin, end) = if ranges.is_empty() {
108 let location = diagnostic.get_location();
109
110 (
111 location.get_file_location().offset as u64,
112 location.get_file_location().offset as u64,
113 )
114 } else {
115 let range = diagnostic.get_ranges()[0];
116 if !range.is_in_main_file() {
117 warn!("Skipping diagnostic: {:?}", diagnostic);
118 return None;
119 }
120 (
121 range.get_start().get_file_location().offset as u64,
122 range.get_end().get_file_location().offset as u64,
123 )
124 };
125
126 let label = SpanLabel {
127 span: file_span.subspan(begin, end),
128 label: None,
129 style: SpanStyle::Primary,
130 };
131
132 Some(Diagnostic {
133 level: match diagnostic.get_severity() {
134 clang::diagnostic::Severity::Ignored => return None,
135 clang::diagnostic::Severity::Note => Level::Note,
136 clang::diagnostic::Severity::Warning => Level::Warning,
137 clang::diagnostic::Severity::Error => Level::Error,
138 clang::diagnostic::Severity::Fatal => Level::Error,
139 },
140 message: diagnostic.get_text(),
141 //code: Some("-Werror=documentation".to_string()),
142 code: None,
143 spans: vec![label],
144 })
145}
146
147fn clang_fix_it_to_codemap_diag(
148 fix_it: &clang::diagnostic::FixIt,
149 file_span: &codemap::Span,
150) -> codemap_diagnostic::Diagnostic {
151 use clang::diagnostic::FixIt;
152 use codemap_diagnostic::{Diagnostic, Level, SpanLabel, SpanStyle};
153
154 let label = match fix_it {
155 FixIt::Deletion(range) => {
156 let begin = range.get_start().get_file_location().offset as u64;
157 let end = range.get_end().get_file_location().offset as u64;
158
159 SpanLabel {
160 span: file_span.subspan(begin, end),
161 label: Some(String::from("Try deleting this")),
162 style: SpanStyle::Primary,
163 }
164 }
165 FixIt::Insertion(range, text) => {
166 let location = range.get_file_location().offset as u64;
167
168 SpanLabel {
169 span: file_span.subspan(location, location),
170 label: Some(format!("Try insterting {:?}", text)),
171 style: SpanStyle::Primary,
172 }
173 }
174 FixIt::Replacement(range, text) => {
175 let begin = range.get_start().get_file_location().offset as u64;
176 let end = range.get_end().get_file_location().offset as u64;
177
178 SpanLabel {
179 span: file_span.subspan(begin, end),
180 label: Some(format!("Try replacing this with {:?}", text)),
181 style: SpanStyle::Primary,
182 }
183 }
184 };
185
186 Diagnostic {
187 level: Level::Help,
188 message: String::from("Possible fix"),
189 //code: Some("-Werror=documentation".to_string()),
190 code: None,
191 spans: vec![label],
192 }
193}
194
195pub(crate) struct Comment(String);
196
197impl Comment {
198 pub fn from_raw(raw: String) -> Self {
199 #[derive(Debug)]
200 enum CommentStyle {
201 // Comments of type `/**` or `/*!`
202 Starred,
203 // Comments of type `///`
204 SingleLine,
205 }
206
207 let mut chars = raw.chars();
208 let style = match &chars.as_str()[..3] {
209 "/*!" | "/**" => CommentStyle::Starred,
210 "///" => CommentStyle::SingleLine,
211 _ => panic!("Comment is empty or doesn't start with either `///`, `/**`, or `/*!`"),
212 };
213
214 chars.nth(2);
215
216 let mut result = String::new();
217
218 'parse_loop: loop {
219 let maybe_space = chars.next();
220 let mut empty_line = false;
221 match maybe_space {
222 // TODO: Warn on empty comments
223 None => break,
224 Some(' ') => {}
225 Some('\n') => {
226 empty_line = true;
227 result.push('\n');
228 }
229 Some(ch) => result.push(ch),
230 }
231
232 if !empty_line {
233 let rest = chars.as_str();
234 match rest.find('\n') {
235 None => {
236 result.push_str(rest);
237 break;
238 }
239 Some(position) => {
240 result.push_str(&rest[..position + 1]);
241 chars.nth(position);
242 }
243 }
244 }
245
246 // Beginning of the line
247 let first_non_ws_ch = 'ws_loop: loop {
248 let maybe_whitespace = chars.next();
249 match maybe_whitespace {
250 None => break 'parse_loop,
251 Some(ch) if ch.is_whitespace() => continue,
252 Some(ch) => break 'ws_loop ch,
253 }
254 };
255
256 match style {
257 CommentStyle::Starred if first_non_ws_ch == '*' => {
258 if &chars.as_str()[..1] == "/" {
259 break;
260 }
261 }
262 CommentStyle::Starred => result.push(first_non_ws_ch),
263 CommentStyle::SingleLine => {
264 assert!(first_non_ws_ch == '/');
265 let rest = chars.as_str();
266 if &rest[..2] == "//" {
267 chars.nth(1);
268 } else if &rest[..1] == "/" {
269 chars.nth(0);
270 } else {
271 panic!("Could not parse comment");
272 }
273 }
274 }
275 }
276
277 Self(result)
278 }
279}
280
281impl From<clang::Usr> for Usr {
282 fn from(usr: clang::Usr) -> Self {
283 Self(usr.0)
284 }
285}
286
287trait FromClangEntity {
288 /// Is responsible for inserting its documentation into the entities manager
289 fn from_clang_entity(entity: clang::Entity, manager: &mut EntitiesManager) -> Self;
290}
291
292trait IntoPoseidocEntity<T> {
293 /// Useful for the `clang_entity.into_poseidoc_entity(&mut manager)` syntax. Implement
294 /// `FromClangEntity` instead.
295 fn into_poseidoc_entity(self, manager: &mut EntitiesManager) -> T;
296}
297
298impl<T> IntoPoseidocEntity<T> for clang::Entity<'_>
299where
300 T: FromClangEntity,
301{
302 fn into_poseidoc_entity(self, manager: &mut EntitiesManager) -> T {
303 T::from_clang_entity(self, manager)
304 }
305}
306
307fn get_description(entity: &clang::Entity) -> Description {
308 let name = entity.get_display_name().unwrap();
309
310 if let (Some(brief), Some(comment)) = (entity.get_comment_brief(), entity.get_comment()) {
311 Description {
312 name,
313 brief,
314 detailed: Comment::from_raw(comment).0,
315 }
316 } else {
317 Description::with_name(name)
318 }
319}
320
321impl FromClangEntity for Entity {
322 fn from_clang_entity(entity: clang::Entity, manager: &mut EntitiesManager) -> Self {
323 use clang::EntityKind;
324
325 match entity.get_kind() {
326 EntityKind::Namespace => Self::NameSpace(entity.into_poseidoc_entity(manager)),
327 EntityKind::FieldDecl | EntityKind::VarDecl => {
328 Self::Variable(entity.into_poseidoc_entity(manager))
329 }
330 EntityKind::FunctionDecl | EntityKind::Method | EntityKind::FunctionTemplate => {
331 Self::Function(entity.into_poseidoc_entity(manager))
332 }
333 EntityKind::ClassDecl | EntityKind::StructDecl | EntityKind::ClassTemplate => {
334 Self::Class(entity.into_poseidoc_entity(manager))
335 }
336 _ => panic!("Unsupported entity: {:?}", entity),
337 }
338 }
339}
340
341impl FromClangEntity for NameSpace {
342 fn from_clang_entity(entity: clang::Entity, manager: &mut EntitiesManager) -> Self {
343 match entity.get_kind() {
344 clang::EntityKind::Namespace => {}
345 _ => panic!("Trying to parse a non-variable into a variable"),
346 }
347 debug!("Parsing NameSpace");
348
349 let members = entity
350 .get_children()
351 .into_iter()
352 .map(|child| {
353 let usr = child.get_usr().unwrap().into();
354 trace!("Namespace has member: {:?}", usr);
355 (usr, child.into_poseidoc_entity(manager))
356 })
357 .collect();
358
359 manager.insert(entity.get_usr().unwrap().into(), get_description(&entity));
360
361 NameSpace { members }
362 }
363}
364
365impl FromClangEntity for Variable {
366 fn from_clang_entity(entity: clang::Entity, manager: &mut EntitiesManager) -> Self {
367 match entity.get_kind() {
368 clang::EntityKind::VarDecl | clang::EntityKind::FieldDecl => {}
369 _ => panic!("Trying to parse a non-variable into a variable"),
370 }
371 debug!("Parsing Variable");
372
373 let r#type = entity.get_type().unwrap().get_display_name();
374 trace!("Variable has type: {:?}", r#type);
375
376 manager.insert(entity.get_usr().unwrap().into(), get_description(&entity));
377
378 Variable { r#type }
379 }
380}
381
382impl FromClangEntity for Function {
383 fn from_clang_entity(entity: clang::Entity, manager: &mut EntitiesManager) -> Self {
384 match entity.get_kind() {
385 clang::EntityKind::Method
386 | clang::EntityKind::FunctionDecl
387 | clang::EntityKind::FunctionTemplate => {}
388 _ => panic!("Trying to parse a non-function into a function"),
389 }
390 debug!("Parsing Function");
391
392 let return_type = entity.get_result_type().unwrap().get_display_name();
393 trace!("Function has return type: {:?}", return_type);
394 let arguments = entity
395 .get_arguments()
396 .unwrap()
397 .into_iter()
398 .map(|arg| {
399 let name = arg.get_display_name().unwrap();
400 let r#type = arg.get_type().unwrap().get_display_name();
401 trace!("Function has argument {:?} of type {:?}", name, r#type);
402 FunctionArgument { name, r#type }
403 })
404 .collect();
405
406 manager.insert(entity.get_usr().unwrap().into(), get_description(&entity));
407
408 Function {
409 arguments,
410 return_type,
411 }
412 }
413}
414
415impl FromClangEntity for Class {
416 fn from_clang_entity(entity: clang::Entity, manager: &mut EntitiesManager) -> Self {
417 match entity.get_kind() {
418 clang::EntityKind::ClassDecl
419 | clang::EntityKind::StructDecl
420 | clang::EntityKind::ClassTemplate => {}
421 _ => panic!("Trying to parse a non-class into a class"),
422 }
423 debug!("Parsing Class");
424
425 let mut member_types = Vec::new();
426 let mut member_functions = HashMap::new();
427 let mut member_variables = HashMap::new();
428
429 for child in entity.get_children() {
430 trace!("Class has child: {:?}", child);
431
432 let child_usr = child.get_usr().unwrap().into();
433
434 use clang::EntityKind;
435 match child.get_kind() {
436 EntityKind::ClassDecl | EntityKind::StructDecl | EntityKind::TypeAliasDecl => {
437 member_types.push(child_usr);
438 }
439 EntityKind::Method => {
440 member_functions.insert(child_usr, child.into_poseidoc_entity(manager));
441 }
442 EntityKind::FieldDecl => {
443 member_variables.insert(child_usr, child.into_poseidoc_entity(manager));
444 }
445 _ => trace!("Skipping child"),
446 }
447 }
448
449 manager.insert(entity.get_usr().unwrap().into(), get_description(&entity));
450
451 Class {
452 member_types,
453 member_functions,
454 member_variables,
455 }
456 }
457}