Skip to content
Snippets Groups Projects
Commit 5fa28508 authored by Martin Geisler's avatar Martin Geisler
Browse files

PDL: add basic support for generating Rust code

This CL adds support for handling a simple PDL file which defines a
packet. The CL includes infrastructure for testing the generated code
against known-good output. This is sometimes called “snapshot
testing”. While there are crates for this, we currently don’t use any
to avoid pulling in new dependencies.

Bug: 228306440
Test: atest pdl_inline_tests
Change-Id: If33403444ba9e4ce9c45e15b65c1da03011aa5e9
parent 2c6d8a46
No related branches found
No related tags found
No related merge requests found
package {
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
......@@ -17,6 +16,11 @@ rust_defaults {
"libserde_json",
"libstructopt",
"libcodespan_reporting",
"libquote",
"libsyn",
"libproc_macro2",
"libanyhow",
"libtempfile",
],
proc_macros: [
"libpest_derive",
......@@ -32,4 +36,8 @@ rust_test_host {
name: "pdl_inline_tests",
defaults: ["pdl_defaults"],
test_suites: ["general-tests"],
data: [
"rustfmt",
"rustfmt.toml",
],
}
../../../../../prebuilts/rust/linux-x86/stable/rustfmt
\ No newline at end of file
../../rustfmt.toml
\ No newline at end of file
This diff is collapsed.
......@@ -4,11 +4,30 @@ use codespan_reporting::term::{self, termcolor};
use structopt::StructOpt;
mod ast;
mod generator;
mod lint;
mod parser;
use crate::lint::Lintable;
#[derive(Debug)]
enum OutputFormat {
JSON,
Rust,
}
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),
_ => Err(format!("could not parse {:?}, valid option are 'json' and 'rust'.", input)),
}
}
}
#[derive(Debug, StructOpt)]
#[structopt(name = "pdl-parser", about = "Packet Description Language parser tool.")]
struct Opt {
......@@ -16,6 +35,11 @@ struct Opt {
#[structopt(short, long = "--version")]
version: bool,
/// Generate output in this format ("json" or "rust"). The output
/// will be printed on stdout in both cases.
#[structopt(short, long = "--output-format", name = "FORMAT", default_value = "JSON")]
output_format: OutputFormat,
/// Input file.
#[structopt(name = "FILE")]
input_file: String,
......@@ -33,7 +57,16 @@ fn main() {
match parser::parse_file(&mut sources, opt.input_file) {
Ok(grammar) => {
let _ = grammar.lint().print(&sources, termcolor::ColorChoice::Always);
println!("{}", serde_json::to_string_pretty(&grammar).unwrap())
match opt.output_format {
OutputFormat::JSON => {
println!("{}", serde_json::to_string_pretty(&grammar).unwrap())
}
OutputFormat::Rust => match generator::generate_rust(&sources, &grammar) {
Ok(code) => println!("{}", &code),
Err(err) => println!("failed to generate code: {}", err),
},
}
}
Err(err) => {
let writer = termcolor::StandardStream::stderr(termcolor::ColorChoice::Always);
......
#[derive(Debug)]
struct FooData {}
#[derive(Debug, Clone)]
pub struct FooPacket {
foo: Arc<FooData>,
}
#[derive(Debug)]
pub struct FooBuilder {}
impl FooData {
fn conforms(bytes: &[u8]) -> bool {
true
}
fn parse(bytes: &[u8]) -> Result<Self> {
Ok(Self {})
}
fn write_to(&self, buffer: &mut BytesMut) {}
fn get_total_size(&self) -> usize {
self.get_size()
}
fn get_size(&self) -> usize {
let ret = 0;
let ret = ret + 0;
ret
}
}
impl Packet for FooPacket {
fn to_bytes(self) -> Bytes {
let mut buffer = BytesMut::new();
buffer.resize(self.foo.get_total_size(), 0);
self.foo.write_to(&mut buffer);
buffer.freeze()
}
fn to_vec(self) -> Vec<u8> {
self.to_bytes().to_vec()
}
}
impl From<FooPacket> for Bytes {
fn from(packet: FooPacket) -> Self {
packet.to_bytes()
}
}
impl From<FooPacket> for Vec<u8> {
fn from(packet: FooPacket) -> Self {
packet.to_vec()
}
}
impl FooPacket {
pub fn parse(bytes: &[u8]) -> Result<Self> {
Ok(Self::new(Arc::new(FooData::parse(bytes)?)).unwrap())
}
fn new(root: Arc<FooData>) -> std::result::Result<Self, &'static str> {
let foo = root;
Ok(Self { foo })
}
}
impl FooBuilder {
pub fn build(self) -> FooPacket {
let foo = Arc::new(FooData {});
FooPacket::new(foo).unwrap()
}
}
#[derive(Debug)]
struct FooData {
x: u8,
y: u16,
}
#[derive(Debug, Clone)]
pub struct FooPacket {
foo: Arc<FooData>,
}
#[derive(Debug)]
pub struct FooBuilder {
pub x: u8,
pub y: u16,
}
impl FooData {
fn conforms(bytes: &[u8]) -> bool {
true
}
fn parse(bytes: &[u8]) -> Result<Self> {
if bytes.len() < 1 {
return Err(Error::InvalidLengthError {
obj: "Foo".to_string(),
field: "x".to_string(),
wanted: 1,
got: bytes.len(),
});
}
let x = u8::from_be_bytes([bytes[0]]);
if bytes.len() < 3 {
return Err(Error::InvalidLengthError {
obj: "Foo".to_string(),
field: "y".to_string(),
wanted: 3,
got: bytes.len(),
});
}
let y = u16::from_be_bytes([bytes[1], bytes[2]]);
Ok(Self { x, y })
}
fn write_to(&self, buffer: &mut BytesMut) {
let x = self.x;
buffer[0..1].copy_from_slice(&x.to_be_bytes()[0..1]);
let y = self.y;
buffer[1..3].copy_from_slice(&y.to_be_bytes()[0..2]);
}
fn get_total_size(&self) -> usize {
self.get_size()
}
fn get_size(&self) -> usize {
let ret = 0;
let ret = ret + 3;
ret
}
}
impl Packet for FooPacket {
fn to_bytes(self) -> Bytes {
let mut buffer = BytesMut::new();
buffer.resize(self.foo.get_total_size(), 0);
self.foo.write_to(&mut buffer);
buffer.freeze()
}
fn to_vec(self) -> Vec<u8> {
self.to_bytes().to_vec()
}
}
impl From<FooPacket> for Bytes {
fn from(packet: FooPacket) -> Self {
packet.to_bytes()
}
}
impl From<FooPacket> for Vec<u8> {
fn from(packet: FooPacket) -> Self {
packet.to_vec()
}
}
impl FooPacket {
pub fn parse(bytes: &[u8]) -> Result<Self> {
Ok(Self::new(Arc::new(FooData::parse(bytes)?)).unwrap())
}
fn new(root: Arc<FooData>) -> std::result::Result<Self, &'static str> {
let foo = root;
Ok(Self { foo })
}
pub fn get_x(&self) -> u8 {
self.foo.as_ref().x
}
pub fn get_y(&self) -> u16 {
self.foo.as_ref().y
}
}
impl FooBuilder {
pub fn build(self) -> FooPacket {
let foo = Arc::new(FooData { x: self.x, y: self.y });
FooPacket::new(foo).unwrap()
}
}
#[derive(Debug)]
struct FooData {
x: u8,
y: u16,
}
#[derive(Debug, Clone)]
pub struct FooPacket {
foo: Arc<FooData>,
}
#[derive(Debug)]
pub struct FooBuilder {
pub x: u8,
pub y: u16,
}
impl FooData {
fn conforms(bytes: &[u8]) -> bool {
true
}
fn parse(bytes: &[u8]) -> Result<Self> {
if bytes.len() < 1 {
return Err(Error::InvalidLengthError {
obj: "Foo".to_string(),
field: "x".to_string(),
wanted: 1,
got: bytes.len(),
});
}
let x = u8::from_le_bytes([bytes[0]]);
if bytes.len() < 3 {
return Err(Error::InvalidLengthError {
obj: "Foo".to_string(),
field: "y".to_string(),
wanted: 3,
got: bytes.len(),
});
}
let y = u16::from_le_bytes([bytes[1], bytes[2]]);
Ok(Self { x, y })
}
fn write_to(&self, buffer: &mut BytesMut) {
let x = self.x;
buffer[0..1].copy_from_slice(&x.to_le_bytes()[0..1]);
let y = self.y;
buffer[1..3].copy_from_slice(&y.to_le_bytes()[0..2]);
}
fn get_total_size(&self) -> usize {
self.get_size()
}
fn get_size(&self) -> usize {
let ret = 0;
let ret = ret + 3;
ret
}
}
impl Packet for FooPacket {
fn to_bytes(self) -> Bytes {
let mut buffer = BytesMut::new();
buffer.resize(self.foo.get_total_size(), 0);
self.foo.write_to(&mut buffer);
buffer.freeze()
}
fn to_vec(self) -> Vec<u8> {
self.to_bytes().to_vec()
}
}
impl From<FooPacket> for Bytes {
fn from(packet: FooPacket) -> Self {
packet.to_bytes()
}
}
impl From<FooPacket> for Vec<u8> {
fn from(packet: FooPacket) -> Self {
packet.to_vec()
}
}
impl FooPacket {
pub fn parse(bytes: &[u8]) -> Result<Self> {
Ok(Self::new(Arc::new(FooData::parse(bytes)?)).unwrap())
}
fn new(root: Arc<FooData>) -> std::result::Result<Self, &'static str> {
let foo = root;
Ok(Self { foo })
}
pub fn get_x(&self) -> u8 {
self.foo.as_ref().x
}
pub fn get_y(&self) -> u16 {
self.foo.as_ref().y
}
}
impl FooBuilder {
pub fn build(self) -> FooPacket {
let foo = Arc::new(FooData { x: self.x, y: self.y });
FooPacket::new(foo).unwrap()
}
}
// @generated rust packets from foo.pdl
use bytes::{BufMut, Bytes, BytesMut};
use num_derive::{FromPrimitive, ToPrimitive};
use num_traits::{FromPrimitive, ToPrimitive};
use std::convert::{TryFrom, TryInto};
use std::fmt;
use std::sync::Arc;
use thiserror::Error;
type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, Error)]
pub enum Error {
#[error("Packet parsing failed")]
InvalidPacketError,
#[error("{field} was {value:x}, which is not known")]
ConstraintOutOfBounds { field: String, value: u64 },
#[error("when parsing {obj}.{field} needed length of {wanted} but got {got}")]
InvalidLengthError { obj: String, field: String, wanted: usize, got: usize },
#[error("Due to size restrictions a struct could not be parsed.")]
ImpossibleStructError,
#[error("when parsing field {obj}.{field}, {value} is not a valid {type_} value")]
InvalidEnumValueError { obj: String, field: String, value: u64, type_: String },
}
#[derive(Debug, Error)]
#[error("{0}")]
pub struct TryFromError(&'static str);
pub trait Packet {
fn to_bytes(self) -> Bytes;
fn to_vec(self) -> Vec<u8>;
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment