thinlinelib/
language_type.rs1use analysis::{Analysis, Argument, Enum, Function};
2use clang;
3use entity::{Entity, EntityType};
4use failure::{err_msg, Fallible};
5use python_parser::ast::{CompoundStatement, Expression, Statement};
6use python_parser::{file_input, make_strspan};
7use std::fs::File;
8use std::io::Read;
9
10lazy_static! {
13 static ref CLANG: Option<clang::Clang> = {
14 match clang::Clang::new() {
15 Ok(clang) => Some(clang),
16 Err(_) => None,
17 }
18 };
19}
20
21pub trait LanguageType: Default {
24 fn file_types() -> &'static [&'static str];
25 fn extract_entities<T: LanguageType>(analysis: &Analysis<T>) -> Fallible<()>;
26}
27
28#[derive(Default, Clone, Debug)]
31struct CFamily;
32
33impl CFamily {
34 fn format_arguments(arguments: &[clang::Entity]) -> Fallible<Vec<Argument>> {
35 let mut args = Vec::new();
36
37 for argument in arguments {
38 args.push(Argument::new(
39 argument.get_display_name().unwrap_or(String::new()),
40 Some(
41 argument
42 .get_type()
43 .ok_or_else(|| err_msg("Argument type can not be parsed from signature."))?
44 .get_display_name(),
45 ),
46 ));
47 }
48
49 Ok(args)
50 }
51
52 fn analyse_clang_function_entity(entity: &clang::Entity) -> Fallible<Option<EntityType>> {
54 if let Some(entity_name) = entity.get_name() {
55 let mut function = Function::new(entity_name);
56
57 if let Some(return_type) = entity.get_type() {
59 function.set_return_type(return_type.get_display_name().as_str())?;
60 }
61
62 if let Some(arguments) = entity.get_arguments() {
64 function.set_arguments(&Self::format_arguments(&arguments)?);
65 }
66
67 if let Some(description) = entity.get_comment() {
69 function.set_description(description.as_str());
70 }
71
72 return Ok(Some(EntityType::Function(function)));
73 }
74
75 Ok(None)
76 }
77
78 fn analyse_clang_enum_entity(entity: &clang::Entity) -> Fallible<Option<EntityType>> {
80 if let Some(entity_name) = entity.get_name() {
81 let enumeration = Enum::new(entity_name);
82
83 return Ok(Some(EntityType::Enum(enumeration)));
84 }
85
86 Ok(None)
87 }
88
89 fn analyse_clang_generic_entity(entity: &clang::Entity) -> Fallible<Option<EntityType>> {
91 if let Some(entity_name) = entity.get_name() {
92 let mut ent = Entity::new(entity_name);
93
94 if let Some(description) = entity.get_comment() {
96 ent.set_description(description.as_str());
97 }
98
99 return Ok(Some(EntityType::Entity(ent)));
100 }
101
102 Ok(None)
103 }
104}
105
106static C_FILE_EXTENSIONS: &[&str] = &["c", "h"];
110
111#[derive(Default, Clone, Debug)]
112pub struct C;
113
114impl C {
115 fn analyse_clang_entity(entity: &clang::Entity) -> Fallible<Option<EntityType>> {
116 let entity_kind = entity.get_kind();
117
118 if !entity.is_in_system_header() {
120 match &entity_kind {
121 clang::EntityKind::FunctionDecl => {
122 return CFamily::analyse_clang_function_entity(entity);
123 }
124 clang::EntityKind::EnumDecl => {
125 return CFamily::analyse_clang_enum_entity(entity);
126 }
127 _ => {}
128 }
129
130 return Ok(None);
131 }
132
133 Ok(None)
134 }
135}
136
137impl LanguageType for C {
138 fn file_types() -> &'static [&'static str] {
139 C_FILE_EXTENSIONS
140 }
141
142 fn extract_entities<C: LanguageType>(analysis: &Analysis<C>) -> Fallible<()> {
143 if let Some(ref clang) = *CLANG {
144 let clang_index = clang::Index::new(&clang, false, false);
145 for project_file in analysis.project_files().iter() {
146 info!("Analyzing '{}'", project_file);
147 if let EntityType::Entity(mut index) = EntityType::Entity(Entity::new("")) {
148 let parsed_path = &clang_index.parser(&project_file.path).parse()?;
149 let clang_entity = parsed_path.get_entity();
150
151 for child in clang_entity.get_children() {
153 if let Ok(Some(entity)) = Self::analyse_clang_entity(&child) {
154 index.add_entity::<Entity>(entity);
155 }
156 }
157
158 debug!("{:#?}", index);
159 project_file.entities_mut().push(index);
160 }
161 }
162 }
163
164 Ok(())
165 }
166}
167
168static CPP_FILE_EXTENSIONS: &[&str] = &["cpp", "hpp"];
172
173#[derive(Default, Clone, Debug)]
174pub struct Cpp;
175
176impl Cpp {
177 fn analyse_clang_entity(entity: &clang::Entity) -> Fallible<Option<EntityType>> {
178 let entity_kind = entity.get_kind();
179
180 if !entity.is_in_system_header() {
182 match &entity_kind {
183 clang::EntityKind::Constructor
184 | clang::EntityKind::Destructor
185 | clang::EntityKind::Method
186 | clang::EntityKind::FunctionDecl => {
187 return CFamily::analyse_clang_function_entity(entity);
188 }
189 clang::EntityKind::EnumDecl => {
190 return CFamily::analyse_clang_enum_entity(entity);
191 }
192 clang::EntityKind::ClassDecl | clang::EntityKind::Namespace => {
193 return CFamily::analyse_clang_generic_entity(entity);
194 }
195 _ => {}
196 }
197
198 return Ok(None);
199 }
200
201 Ok(None)
202 }
203
204 fn analyse_clang_entity_tree(
205 parent: &mut Entity,
206 clang_entity: &clang::Entity,
207 ) -> Fallible<()> {
208 for child in clang_entity.get_children() {
210 if let Ok(Some(entity)) = Self::analyse_clang_entity(&child) {
211 if let Some(added_entity) = parent.add_entity(entity) {
212 Self::analyse_clang_entity_tree(added_entity, &child)?;
213 }
214 }
215 }
216
217 Ok(())
218 }
219}
220
221impl LanguageType for Cpp {
222 fn file_types() -> &'static [&'static str] {
223 CPP_FILE_EXTENSIONS
224 }
225
226 fn extract_entities<Cpp: LanguageType>(analysis: &Analysis<Cpp>) -> Fallible<()> {
227 if let Some(ref clang) = *CLANG {
228 let clang_index = clang::Index::new(&clang, false, false);
229 for project_file in analysis.project_files().iter() {
230 info!("Analyzing '{}'", project_file);
231 if let EntityType::Entity(mut index) = EntityType::Entity(Entity::new("")) {
232 let parsed_path = &clang_index.parser(&project_file.path).parse()?;
233 let clang_entity = parsed_path.get_entity();
234
235 Self::analyse_clang_entity_tree(&mut index, &clang_entity)?;
236
237 debug!("{:#?}", index);
238 project_file.entities_mut().push(index);
239 }
240 }
241 }
242
243 Ok(())
244 }
245}
246
247static PYTHON_FILE_EXTENSIONS: &[&str] = &["py"];
251
252#[derive(Default, Clone, Debug)]
253pub struct Python;
254
255impl Python {
256 fn extract_function_doc(function: &mut Function, statement: &Statement) {
257 if let Statement::Assignment(ent_v, _) = statement {
258 for ent in ent_v.iter() {
259 if let Expression::String(expr_v) = ent {
260 for expr in expr_v.iter() {
261 function.set_description(&expr.content.to_string_lossy());
262 }
263 }
264 }
265 }
266 }
267
268 fn analyse_statement(entity: &mut Entity, statement: &Statement) -> Fallible<()> {
269 if let Statement::Compound(ent_box) = statement {
270 match Box::leak((*ent_box).clone()) {
271 CompoundStatement::Funcdef(expr) => {
273 let mut function: Function = Function::new(expr.name.as_str());
274
275 let mut arguments: Vec<Argument> = Vec::new();
277 for arg in &expr.parameters.positional_args {
278 arguments.push(Argument::new(arg.0.as_str(), None));
279 }
280 function.set_arguments(&arguments);
281
282 if let Some(mut function_inst) =
283 entity.add_entity(EntityType::Function(function))
284 {
285 Self::extract_function_doc(&mut function_inst, &expr.code[0]);
286 }
287 }
288
289 CompoundStatement::Classdef(expr) => {
291 if let Some(ref mut class_entity) =
292 entity.add_entity(EntityType::Entity(Entity::new(expr.name.as_str())))
293 {
294 for code in &expr.code {
295 Self::analyse_statement(class_entity, &code)?;
296 }
297 }
298 }
299 _ => {}
300 }
301 }
302
303 Ok(())
304 }
305}
306
307impl LanguageType for Python {
308 fn file_types() -> &'static [&'static str] {
309 PYTHON_FILE_EXTENSIONS
310 }
311
312 fn extract_entities<Python: LanguageType>(analysis: &Analysis<Python>) -> Fallible<()> {
313 for project_file in analysis.project_files().iter() {
314 info!("Analyzing '{}'", project_file);
315
316 let mut file = File::open(&project_file.path)?;
318 let mut content = String::new();
319 file.read_to_string(&mut content)?;
320
321 if let EntityType::Entity(mut index) = EntityType::Entity(Entity::new("")) {
322 match file_input(make_strspan(content.as_str())) {
323 Ok(ast) => {
324 for entity in ast.1.iter() {
325 Self::analyse_statement(&mut index, entity)?;
326 }
327 }
328 Err(_) => bail!("Unable to create python AST."),
329 }
330 debug!("{:#?}", index);
331 project_file.entities_mut().push(index);
332 }
333 }
334
335 Ok(())
336 }
337}
338
339#[cfg(test)]
342mod c {
343 use super::{Analysis, LanguageType, C, C_FILE_EXTENSIONS};
344
345 #[test]
346 fn new() {
347 let analysis: Analysis<C> = Analysis::new();
348
349 assert_eq!(analysis.file_types, C_FILE_EXTENSIONS);
350 assert_eq!(analysis.project_files().len(), 0);
351 }
352
353 #[test]
354 fn file_types() {
355 assert_eq!(C::file_types(), C_FILE_EXTENSIONS);
356 }
357}
358
359#[cfg(test)]
360mod cpp {
361 use super::{Analysis, Cpp, LanguageType, CPP_FILE_EXTENSIONS};
362
363 #[test]
364 fn new() {
365 let analysis: Analysis<Cpp> = Analysis::new();
366
367 assert_eq!(analysis.file_types, CPP_FILE_EXTENSIONS);
368 assert_eq!(analysis.project_files().len(), 0);
369 }
370
371 #[test]
372 fn file_types() {
373 assert_eq!(Cpp::file_types(), CPP_FILE_EXTENSIONS);
374 }
375}
376
377#[cfg(test)]
378mod python {
379 use super::{Analysis, LanguageType, Python, PYTHON_FILE_EXTENSIONS};
380
381 #[test]
382 fn new() {
383 let analysis: Analysis<Python> = Analysis::new();
384
385 assert_eq!(analysis.file_types, PYTHON_FILE_EXTENSIONS);
386 assert_eq!(analysis.project_files().len(), 0);
387 }
388
389 #[test]
390 fn file_types() {
391 assert_eq!(Python::file_types(), PYTHON_FILE_EXTENSIONS);
392 }
393}