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}