| // Copyright 2023 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| //! PDL parser and analyzer. |
| |
| use argh::FromArgs; |
| use codespan_reporting::term::{self, termcolor}; |
| |
| use pdl_compiler::{analyzer, ast, backends, parser}; |
| |
| #[allow(clippy::upper_case_acronyms)] |
| #[derive(Copy, Clone, Debug, Eq, PartialEq)] |
| enum OutputFormat { |
| JSON, |
| Rust, |
| RustLegacy, |
| RustNoAlloc, |
| } |
| |
| impl std::str::FromStr for OutputFormat { |
| type Err = String; |
| |
| fn from_str(input: &str) -> Result<Self, Self::Err> { |
| match input.to_lowercase().as_str() { |
| "json" => Ok(Self::JSON), |
| "rust" => Ok(Self::Rust), |
| "rust_legacy" => Ok(Self::RustLegacy), |
| "rust_no_alloc" => Ok(Self::RustNoAlloc), |
| _ => Err(format!("could not parse {:?}, valid option are 'json', 'rust', 'rust_no_alloc', and 'rust_no_alloc_test'.", input)), |
| } |
| } |
| } |
| |
| #[derive(FromArgs, Debug)] |
| /// PDL analyzer and generator. |
| struct Opt { |
| #[argh(switch)] |
| /// print tool version and exit. |
| version: bool, |
| |
| #[argh(option, default = "OutputFormat::JSON")] |
| /// generate output in this format ("json", "rust", "rust_legacy", "rust_no_alloc"). |
| /// The output will be printed on stdout in all cases. |
| /// The input file is the source PDL file. |
| output_format: OutputFormat, |
| |
| #[argh(switch)] |
| /// generate tests for the selected output format. |
| /// Valid for the output formats "rust_legacy", "rust_no_alloc". |
| /// The input file must point to a JSON formatterd file with the list of |
| /// test vectors. |
| tests: bool, |
| |
| #[argh(positional)] |
| /// input file. |
| input_file: String, |
| |
| #[argh(option)] |
| /// exclude declarations from the generated output. |
| exclude_declaration: Vec<String>, |
| |
| #[argh(option)] |
| /// custom_field import paths. |
| /// For the rust backend this is a path e.g. "module::CustomField" or "super::CustomField". |
| custom_field: Vec<String>, |
| } |
| |
| /// Remove declarations listed in the input filter. |
| fn filter_declarations(file: ast::File, exclude_declarations: &[String]) -> ast::File { |
| ast::File { |
| declarations: file |
| .declarations |
| .into_iter() |
| .filter(|decl| { |
| decl.id().map(|id| !exclude_declarations.contains(&id.to_owned())).unwrap_or(true) |
| }) |
| .collect(), |
| ..file |
| } |
| } |
| |
| fn generate_backend(opt: &Opt) -> Result<(), String> { |
| let mut sources = ast::SourceDatabase::new(); |
| match parser::parse_file(&mut sources, &opt.input_file) { |
| Ok(file) => { |
| let file = filter_declarations(file, &opt.exclude_declaration); |
| let analyzed_file = match analyzer::analyze(&file) { |
| Ok(file) => file, |
| Err(diagnostics) => { |
| diagnostics |
| .emit( |
| &sources, |
| &mut termcolor::StandardStream::stderr(termcolor::ColorChoice::Always) |
| .lock(), |
| ) |
| .expect("Could not print analyzer diagnostics"); |
| return Err(String::from("Analysis failed")); |
| } |
| }; |
| |
| match opt.output_format { |
| OutputFormat::JSON => { |
| println!("{}", backends::json::generate(&file).unwrap()) |
| } |
| OutputFormat::Rust => { |
| println!( |
| "{}", |
| backends::rust::generate(&sources, &analyzed_file, &opt.custom_field) |
| ) |
| } |
| OutputFormat::RustLegacy => { |
| println!("{}", backends::rust_legacy::generate(&sources, &analyzed_file)) |
| } |
| OutputFormat::RustNoAlloc => { |
| let schema = backends::intermediate::generate(&file).unwrap(); |
| println!("{}", backends::rust_no_allocation::generate(&file, &schema).unwrap()) |
| } |
| } |
| Ok(()) |
| } |
| |
| Err(err) => { |
| let writer = termcolor::StandardStream::stderr(termcolor::ColorChoice::Always); |
| let config = term::Config::default(); |
| term::emit(&mut writer.lock(), &config, &sources, &err).expect("Could not print error"); |
| Err(String::from("Error while parsing input")) |
| } |
| } |
| } |
| |
| fn generate_tests(opt: &Opt) -> Result<(), String> { |
| match opt.output_format { |
| OutputFormat::Rust => { |
| println!("{}", backends::rust::test::generate_tests(&opt.input_file)?) |
| } |
| OutputFormat::RustLegacy => { |
| println!("{}", backends::rust_legacy::test::generate_tests(&opt.input_file)?) |
| } |
| OutputFormat::RustNoAlloc => { |
| println!("{}", backends::rust_no_allocation::test::generate_test_file()?) |
| } |
| _ => { |
| return Err(format!( |
| "Canonical tests cannot be generated for the format {:?}", |
| opt.output_format |
| )) |
| } |
| } |
| Ok(()) |
| } |
| |
| fn main() -> Result<(), String> { |
| let opt: Opt = argh::from_env(); |
| |
| if opt.version { |
| println!("Packet Description Language parser version 1.0"); |
| return Ok(()); |
| } |
| |
| if opt.tests { |
| generate_tests(&opt) |
| } else { |
| generate_backend(&opt) |
| } |
| } |