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