aidl_parser/
parser.rs

1use std::{
2    collections::HashMap,
3    fmt::Debug,
4    hash::Hash,
5    io::Read,
6    path::{Path, PathBuf},
7};
8
9use crate::ast;
10use crate::diagnostic::Diagnostic;
11use crate::rules;
12use crate::validation;
13
14/// A parser instance which receives the individual AIDL files via
15/// Parser::add_content() or Parser::add_file(). Once all the files
16/// have been added, call Parser::parser() to trigger the validation
17/// and access the results.
18///
19/// The ID of the files added to the parser are used to uniquely
20/// identify the results returned by the parser. It can be any
21/// value used as a key (e.g. number of string) or the location of
22/// the content (e.g. PathBuf or Uri).
23///
24/// The content added to the parser can be removed or replaced
25/// before or after the parsing.
26///
27/// Example:
28/// ```
29/// use aidl_parser::{Parser, ParseFileResult};
30///
31/// let mut parser = Parser::new();
32///
33/// // Add files via ID + content
34/// parser.add_content("id1", "<content of AIDL file #1>");
35/// parser.add_content("id2", "<content of AIDL file #2>");
36/// parser.add_content("id3", "<content of AIDL file #3>");
37///
38/// // Parse and get results
39/// let results = parser.validate();
40///
41/// assert_eq!(results.len(), 3);
42/// assert!(results.contains_key("id1"));
43/// assert!(results.contains_key("id2"));
44/// assert!(results.contains_key("id3"));
45///
46/// // Add/replace/remove files
47/// parser.add_content("id2", "<updated content of AIDL file #2>");
48/// parser.add_content("id4", "<content of AIDL file #4>");
49/// parser.add_content("id5", "<content of AIDL file #5>");
50/// parser.remove_content("id3");
51///
52/// // Parse again and get updated results
53/// let results = parser.validate();
54///
55/// assert_eq!(results.len(), 4);
56/// assert!(results.contains_key("id1"));
57/// assert!(results.contains_key("id2"));
58/// assert!(!results.contains_key("id3"));  // removed
59/// assert!(results.contains_key("id4"));
60/// assert!(results.contains_key("id5"));
61/// ```
62pub struct Parser<ID>
63where
64    ID: Eq + Hash + Clone + Debug,
65{
66    lalrpop_results: HashMap<ID, ParseFileResult<ID>>,
67}
68
69/// The parse result of 1 file with its corresponding ID as given via
70/// Parser::add_content() or Parser::add_file().
71#[derive(Debug, Clone)]
72pub struct ParseFileResult<ID>
73where
74    ID: Eq + Hash + Clone + Debug,
75{
76    pub id: ID,
77    pub ast: Option<ast::Aidl>,
78    pub diagnostics: Vec<Diagnostic>,
79}
80
81impl<ID> Parser<ID>
82where
83    ID: Eq + Hash + Clone + Debug,
84{
85    /// Create a new, empty parser
86    pub fn new() -> Self {
87        Parser {
88            lalrpop_results: HashMap::new(),
89        }
90    }
91
92    /// Add a file content and its key to the parser.
93    ///
94    /// This will parse the individual content and store the result internally.
95    ///
96    /// Note: if a content with the same id already exists, the old content will be replaced.
97    pub fn add_content(&mut self, id: ID, content: &str) {
98        let lookup = line_col::LineColLookup::new(content);
99        let mut diagnostics = Vec::new();
100
101        let rule_result =
102            rules::aidl::OptAidlParser::new().parse(&lookup, &mut diagnostics, content);
103
104        let lalrpop_result = match rule_result {
105            Ok(file) => ParseFileResult {
106                id: id.clone(),
107                ast: file,
108                diagnostics,
109            },
110            Err(e) => {
111                // Append the parse error to the diagnostics
112                if let Some(diagnostic) = Diagnostic::from_parse_error(&lookup, e) {
113                    diagnostics.push(diagnostic)
114                }
115
116                ParseFileResult {
117                    id: id.clone(),
118                    ast: None,
119                    diagnostics,
120                }
121            }
122        };
123
124        self.lalrpop_results.insert(id, lalrpop_result);
125    }
126
127    /// Remove the file with the given key
128    pub fn remove_content(&mut self, id: ID) {
129        self.lalrpop_results.remove(&id);
130    }
131
132    /// Validate the results of all files previously added to the parser and return the
133    /// collected results (AST + diagnostics)
134    pub fn validate(&self) -> HashMap<ID, ParseFileResult<ID>> {
135        let keys = self.collect_item_keys();
136        validation::validate(keys, self.lalrpop_results.clone())
137    }
138
139    fn collect_item_keys(&self) -> HashMap<ast::ItemKey, ast::ResolvedItemKind> {
140        self.lalrpop_results
141            .values()
142            .flat_map(|fr| &fr.ast)
143            .map(|f| (f.get_key(), f.item.get_kind()))
144            .collect()
145    }
146}
147
148impl Parser<PathBuf> {
149    /// Add a file to the parser and use its path as key.
150    ///
151    /// If a file with the same path already exists, the old file will be replaced.
152    pub fn add_file<P: AsRef<Path>>(&mut self, path: P) -> std::io::Result<()> {
153        let mut file = std::fs::File::open(path.as_ref())?;
154        let mut buffer = String::new();
155        file.read_to_string(&mut buffer)?;
156
157        self.add_content(PathBuf::from(path.as_ref()), &buffer);
158        Ok(())
159    }
160}
161
162impl<ID> Default for Parser<ID>
163where
164    ID: Eq + Hash + Clone + Debug,
165{
166    fn default() -> Self {
167        Self::new()
168    }
169}
170
171#[cfg(test)]
172mod test {
173    use super::*;
174    use anyhow::Result;
175
176    #[test]
177    fn test_validate() -> Result<()> {
178        let interface_aidl = r#"
179            package com.bwa.aidl_test;
180        
181            import com.bwa.aidl_test.MyEnum;
182            import com.bwa.aidl_test.MyEnum;
183            import com.bwa.aidl_test.MyEnum;
184            import com.bwa.aidl_test.MyParcelable;
185            import com.bwa.aidl_test.MyUnexisting;
186
187            interface MyInterface {
188                const int MY_CONST = 12;
189                /**
190                 * Be polite and say hello
191                 */
192                //String hello(MyEnum e, MyParcelable);
193                String servus(MyEnum e, MyWrong);
194                String bonjour(MyEnum e, MyUnexisting);
195            }
196        "#;
197
198        let enum_aidl = r#"
199            package com.bwa.aidl_test;
200        
201            enum MyEnum {
202                VALUE1 = 1,
203                VALUE2 = 2,
204            }
205        "#;
206
207        let parcelable_aidl = r#"
208            package com.bwa.aidl_test;
209        
210            parcelable MyParcelable {
211                String name;
212                byte[] data;
213            }
214        "#;
215
216        // Parse AIDL files
217        let mut parser = Parser::new();
218        parser.add_content(0, interface_aidl);
219        parser.add_content(1, parcelable_aidl);
220        parser.add_content(2, enum_aidl);
221        let res = parser.validate();
222
223        // For each file, 1 result
224        assert_eq!(res.len(), 3);
225
226        // No error/warning
227        println!("...\nDiagnostics 1:\n{:#?}", res[&0].diagnostics);
228        println!("...\nDiagnostics 2:\n{:#?}", res[&1].diagnostics);
229        println!("...\nDiagnostics 3:\n{:#?}", res[&2].diagnostics);
230
231        Ok(())
232    }
233}