1 // Copyright 2023 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 //! PDL parser and analyzer.
16 
17 use argh::FromArgs;
18 use codespan_reporting::term::{self, termcolor};
19 
20 use pdl_compiler::{analyzer, ast, backends, parser};
21 
22 #[allow(clippy::upper_case_acronyms)]
23 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
24 enum OutputFormat {
25     JSON,
26     Rust,
27     RustLegacy,
28     RustNoAlloc,
29 }
30 
31 impl std::str::FromStr for OutputFormat {
32     type Err = String;
33 
from_str(input: &str) -> Result<Self, Self::Err>34     fn from_str(input: &str) -> Result<Self, Self::Err> {
35         match input.to_lowercase().as_str() {
36             "json" => Ok(Self::JSON),
37             "rust" => Ok(Self::Rust),
38             "rust_legacy" => Ok(Self::RustLegacy),
39             "rust_no_alloc" => Ok(Self::RustNoAlloc),
40             _ => Err(format!("could not parse {:?}, valid option are 'json', 'rust', 'rust_no_alloc', and 'rust_no_alloc_test'.", input)),
41         }
42     }
43 }
44 
45 #[derive(FromArgs, Debug)]
46 /// PDL analyzer and generator.
47 struct Opt {
48     #[argh(switch)]
49     /// print tool version and exit.
50     version: bool,
51 
52     #[argh(option, default = "OutputFormat::JSON")]
53     /// generate output in this format ("json", "rust", "rust_legacy", "rust_no_alloc").
54     /// The output will be printed on stdout in all cases.
55     /// The input file is the source PDL file.
56     output_format: OutputFormat,
57 
58     #[argh(switch)]
59     /// generate tests for the selected output format.
60     /// Valid for the output formats "rust_legacy", "rust_no_alloc".
61     /// The input file must point to a JSON formatterd file with the list of
62     /// test vectors.
63     tests: bool,
64 
65     #[argh(positional)]
66     /// input file.
67     input_file: String,
68 
69     #[argh(option)]
70     /// exclude declarations from the generated output.
71     exclude_declaration: Vec<String>,
72 
73     #[argh(option)]
74     /// custom_field import paths.
75     /// For the rust backend this is a path e.g. "module::CustomField" or "super::CustomField".
76     custom_field: Vec<String>,
77 }
78 
79 /// Remove declarations listed in the input filter.
filter_declarations(file: ast::File, exclude_declarations: &[String]) -> ast::File80 fn filter_declarations(file: ast::File, exclude_declarations: &[String]) -> ast::File {
81     ast::File {
82         declarations: file
83             .declarations
84             .into_iter()
85             .filter(|decl| {
86                 decl.id().map(|id| !exclude_declarations.contains(&id.to_owned())).unwrap_or(true)
87             })
88             .collect(),
89         ..file
90     }
91 }
92 
generate_backend(opt: &Opt) -> Result<(), String>93 fn generate_backend(opt: &Opt) -> Result<(), String> {
94     let mut sources = ast::SourceDatabase::new();
95     match parser::parse_file(&mut sources, &opt.input_file) {
96         Ok(file) => {
97             let file = filter_declarations(file, &opt.exclude_declaration);
98             let analyzed_file = match analyzer::analyze(&file) {
99                 Ok(file) => file,
100                 Err(diagnostics) => {
101                     diagnostics
102                         .emit(
103                             &sources,
104                             &mut termcolor::StandardStream::stderr(termcolor::ColorChoice::Always)
105                                 .lock(),
106                         )
107                         .expect("Could not print analyzer diagnostics");
108                     return Err(String::from("Analysis failed"));
109                 }
110             };
111 
112             match opt.output_format {
113                 OutputFormat::JSON => {
114                     println!("{}", backends::json::generate(&file).unwrap())
115                 }
116                 OutputFormat::Rust => {
117                     println!(
118                         "{}",
119                         backends::rust::generate(&sources, &analyzed_file, &opt.custom_field)
120                     )
121                 }
122                 OutputFormat::RustLegacy => {
123                     println!("{}", backends::rust_legacy::generate(&sources, &analyzed_file))
124                 }
125                 OutputFormat::RustNoAlloc => {
126                     let schema = backends::intermediate::generate(&file).unwrap();
127                     println!("{}", backends::rust_no_allocation::generate(&file, &schema).unwrap())
128                 }
129             }
130             Ok(())
131         }
132 
133         Err(err) => {
134             let writer = termcolor::StandardStream::stderr(termcolor::ColorChoice::Always);
135             let config = term::Config::default();
136             term::emit(&mut writer.lock(), &config, &sources, &err).expect("Could not print error");
137             Err(String::from("Error while parsing input"))
138         }
139     }
140 }
141 
generate_tests(opt: &Opt) -> Result<(), String>142 fn generate_tests(opt: &Opt) -> Result<(), String> {
143     match opt.output_format {
144         OutputFormat::Rust => {
145             println!("{}", backends::rust::test::generate_tests(&opt.input_file)?)
146         }
147         OutputFormat::RustLegacy => {
148             println!("{}", backends::rust_legacy::test::generate_tests(&opt.input_file)?)
149         }
150         OutputFormat::RustNoAlloc => {
151             println!("{}", backends::rust_no_allocation::test::generate_test_file()?)
152         }
153         _ => {
154             return Err(format!(
155                 "Canonical tests cannot be generated for the format {:?}",
156                 opt.output_format
157             ))
158         }
159     }
160     Ok(())
161 }
162 
main() -> Result<(), String>163 fn main() -> Result<(), String> {
164     let opt: Opt = argh::from_env();
165 
166     if opt.version {
167         println!("Packet Description Language parser version 1.0");
168         return Ok(());
169     }
170 
171     if opt.tests {
172         generate_tests(&opt)
173     } else {
174         generate_backend(&opt)
175     }
176 }
177