Skip to content

Add several macro attributes #26

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
target
Cargo.lock
.idea
2 changes: 1 addition & 1 deletion examples/get_enum_discriminant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ pub struct Goodbye {
reason: String,
}

#[protocol(discriminant = "integer")]
#[derive(Protocol, Clone, Debug, PartialEq)]
#[protocol(discriminant = "integer")]
#[repr(u16)]
pub enum PacketKind {
#[protocol(discriminator(0x00))]
Expand Down
2 changes: 1 addition & 1 deletion protocol-derive/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ proc-macro = true
# N.B. protocol-derive should not depend on the protocol crate.
# This causes releasing to be a pain - which one first - neither is possible!
[dependencies]
syn = { version = "1.0.60", features = ["default", "extra-traits"] }
syn = { version = "1.0.60", features = ["default", "extra-traits", "parsing"] }
quote = "1.0.9"
proc-macro2 = "1.0.24"

113 changes: 101 additions & 12 deletions protocol-derive/src/attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ use crate::format::{self, Format};

use proc_macro2::{Span, TokenStream};
use syn;
use syn::{ExprPath, ExprBinary, ExprUnary, Expr};
use quote::ToTokens;

#[derive(Debug)]
pub enum Protocol {
Expand All @@ -12,6 +14,38 @@ pub enum Protocol {
prefix_field_name: syn::Ident,
prefix_subfield_names: Vec<syn::Ident>,
},
FixedLength(usize),
SkipIf(SkipExpression),
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum SkipExpression {
PathExp(ExprPath),
BinaryExp(ExprBinary),
UnaryExp(ExprUnary),
Path(syn::Path),
}

impl SkipExpression {
pub fn parse_from(exp: &str) -> SkipExpression {
let expr = syn::parse_str::<Expr>(exp).unwrap();

match expr {
Expr::Binary(e) => SkipExpression::BinaryExp(e),
Expr::Unary(e) => SkipExpression::UnaryExp(e),
Expr::Path(e) => SkipExpression::PathExp(e),
_ => panic!("Unexpected skip expression")
}
}

pub fn to_token_stream(&self) -> TokenStream {
match self {
SkipExpression::PathExp(e) => e.to_token_stream(),
SkipExpression::BinaryExp(e) => e.to_token_stream(),
SkipExpression::UnaryExp(e) => e.to_token_stream(),
SkipExpression::Path(e) => e.to_token_stream(),
}
}
}

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
Expand All @@ -36,7 +70,7 @@ pub fn repr(attrs: &[syn::Attribute]) -> Option<syn::Ident> {
}

pub fn protocol(attrs: &[syn::Attribute])
-> Option<Protocol> {
-> Option<Protocol> {
let meta_list = attrs.iter().filter_map(|attr| match attr.parse_meta() {
Ok(syn::Meta::List(meta_list)) => {
if meta_list.path.get_ident() == Some(&syn::Ident::new("protocol", proc_macro2::Span::call_site())) {
Expand All @@ -45,20 +79,46 @@ pub fn protocol(attrs: &[syn::Attribute])
// Unrelated attribute.
None
}
},
}
_ => None,
}).next();

let meta_list: syn::MetaList = if let Some(meta_list) = meta_list { meta_list } else { return None };
let meta_list: syn::MetaList = if let Some(meta_list) = meta_list { meta_list } else { return None; };
let mut nested_metas = meta_list.nested.into_iter();

match nested_metas.next() {
Some(syn::NestedMeta::Meta(syn::Meta::List(nested_list))) => {
match &nested_list.path.get_ident().expect("meta is not an ident").to_string()[..] {
// #[protocol(length_prefix(<kind>(<prefix field name>)))]
"skip_if" => {
let expression = expect::meta_list::single_element(nested_list).unwrap();
let expression = match expression {
syn::NestedMeta::Lit(syn::Lit::Str(s)) => {
SkipExpression::parse_from(&s.value())
}
syn::NestedMeta::Meta(syn::Meta::Path(path)) => {
SkipExpression::Path(path)
}
_ => panic!("Expected a path, binary or unary expression")
};

Some(Protocol::SkipIf(expression))
}
"fixed_length" => {
let nested_list = expect::meta_list::single_literal(nested_list)
.expect("expected a nested list");

match nested_list {
syn::Lit::Int(len) => {
let len = len.base10_parse::<usize>().expect("Invalid fixed length, expected usize");
Some(Protocol::FixedLength(len))
}
_ => panic!("Invalid fixed length, expected usize")
}
}
"length_prefix" => {
let nested_list = expect::meta_list::nested_list(nested_list)
.expect("expected a nested list");
.expect("expected a nested list");
let prefix_kind = match &nested_list.path.get_ident().expect("nested list is not an ident").to_string()[..] {
"bytes" => LengthPrefixKind::Bytes,
"elements" => LengthPrefixKind::Elements,
Expand All @@ -69,9 +129,9 @@ pub fn protocol(attrs: &[syn::Attribute])
let (prefix_field_name, prefix_subfield_names) = match length_prefix_expr {
syn::NestedMeta::Lit(syn::Lit::Str(s)) => {
let mut parts: Vec<_> = s.value()
.split(".")
.map(|s| syn::Ident::new(s, Span::call_site()))
.collect();
.split(".")
.map(|s| syn::Ident::new(s, Span::call_site()))
.collect();

if parts.len() < 1 {
panic!("there must be at least one field mentioned");
Expand All @@ -81,7 +141,7 @@ pub fn protocol(attrs: &[syn::Attribute])
let subfield_idents = parts.into_iter().collect();

(field_ident, subfield_idents)
},
}
syn::NestedMeta::Meta(syn::Meta::Path(path)) => match path.get_ident() {
Some(field_ident) => (field_ident.clone(), Vec::new()),
None => panic!("path is not an ident"),
Expand All @@ -90,15 +150,15 @@ pub fn protocol(attrs: &[syn::Attribute])
};

Some(Protocol::LengthPrefix { kind: prefix_kind, prefix_field_name, prefix_subfield_names })
},
}
"discriminator" => {
let literal = expect::meta_list::single_literal(nested_list)
.expect("expected a single literal");
.expect("expected a single literal");
Some(Protocol::Discriminator(literal))
},
}
name => panic!("#[protocol({})] is not valid", name),
}
},
}
Some(syn::NestedMeta::Meta(syn::Meta::NameValue(name_value))) => {
match name_value.path.get_ident() {
Some(ident) => {
Expand Down Expand Up @@ -185,3 +245,32 @@ mod attribute {
}
}

#[cfg(test)]
mod test {
use crate::attr::SkipExpression;

#[test]
fn should_parse_skip_expression() {
let binary = "a == b";
let parse_result = SkipExpression::parse_from(binary);
assert!(matches!(parse_result, SkipExpression::BinaryExp(_)));

let unary = "!b";
let parse_result = SkipExpression::parse_from(unary);
assert!(matches!(parse_result, SkipExpression::UnaryExp(_)));

let path = "hello";
let parse_result = SkipExpression::parse_from(path);
assert!(matches!(parse_result, SkipExpression::PathExp(_)));
}

#[test]
fn should_convert_expression_to_token() {
let binary = "a == b";
let parse_result = SkipExpression::parse_from(binary);
let tokens = parse_result.to_token_stream();
let expression = quote! { #tokens };
assert_eq!(expression.to_string(), "a == b");
}
}

2 changes: 1 addition & 1 deletion protocol-derive/src/codegen/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ pub fn read_variant(plan: &plan::Enum)
let discriminator_match_branches = plan.variants.iter().map(|variant| {
let variant_name = &variant.ident;
let discriminator_literal = variant.discriminator_literal();
let initializer = codegen::read_fields(&variant.fields);
let initializer = codegen::read_enum_fields(&variant.fields);

quote! {
#discriminator_literal => {
Expand Down
Loading