blob: e8fce660124dcf93c5ef781f519dbff2523fcfa4 [file] [log] [blame] [edit]
// 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)
}
}