// 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 { 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, #[argh(option)] /// custom_field import paths. /// For the rust backend this is a path e.g. "module::CustomField" or "super::CustomField". custom_field: Vec, } /// 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) } }