thinlinelib/
language_type.rs

1use 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
10////////////////////////////////////////////////////////////////////////////////
11
12lazy_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
21////////////////////////////////////////////////////////////////////////////////
22
23pub trait LanguageType: Default {
24    fn file_types() -> &'static [&'static str];
25    fn extract_entities<T: LanguageType>(analysis: &Analysis<T>) -> Fallible<()>;
26}
27
28////////////////////////////////////////////////////////////////////////////////
29
30#[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    /// Analyzes a clang function entity and returns the connected EntityType::Function
53    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            // Set return type.
58            if let Some(return_type) = entity.get_type() {
59                function.set_return_type(return_type.get_display_name().as_str())?;
60            }
61
62            // Set arguments vector.
63            if let Some(arguments) = entity.get_arguments() {
64                function.set_arguments(&Self::format_arguments(&arguments)?);
65            }
66
67            // Set description.
68            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    /// Analyzes a clang enumeration entity and returns the connected EntityType::Enum
79    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    /// Analyzes a generic clang entity and returns the connected EntityType::Entity
90    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            // Set description.
95            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
106////////////////////////////////////////////////////////////////////////////////
107
108/// The file extensions which should be checked for C project analysis.
109static 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        // Search for functions outside the system headers
119        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                    // Iterate through the child entities of the current entity
152                    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
168////////////////////////////////////////////////////////////////////////////////
169
170/// The file extensions which should be checked for C++ project analysis.
171static 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        // Search for functions outside the system headers
181        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        // Iterate through the child entities of the current entity
209        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
247////////////////////////////////////////////////////////////////////////////////
248
249/// The file extensions which should be checked for Python project analysis.
250static 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                // Statement is a statement definition
272                CompoundStatement::Funcdef(expr) => {
273                    let mut function: Function = Function::new(expr.name.as_str());
274
275                    // Split arguments and add them to the function
276                    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                // Statement is a class definition
290                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            // Parse file to string
317            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////////////////////////////////////////////////////////////////////////////////
340
341#[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}