diff options
Diffstat (limited to 'src/bindgen/bitflags.rs')
-rw-r--r-- | src/bindgen/bitflags.rs | 289 |
1 files changed, 289 insertions, 0 deletions
diff --git a/src/bindgen/bitflags.rs b/src/bindgen/bitflags.rs new file mode 100644 index 0000000..2ca0be6 --- /dev/null +++ b/src/bindgen/bitflags.rs @@ -0,0 +1,289 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use proc_macro2::TokenStream; +use std::collections::HashSet; +use syn::fold::Fold; +use syn::parse::{Parse, ParseStream, Parser, Result as ParseResult}; + +// $(#[$outer:meta])* +// ($($vis:tt)*) $BitFlags:ident: $T:ty { +// $( +// $(#[$inner:ident $($args:tt)*])* +// const $Flag:ident = $value:expr; +// )+ +// } +#[derive(Debug)] +pub struct BitflagsStruct { + attrs: Vec<syn::Attribute>, + vis: syn::Visibility, + #[allow(dead_code)] + struct_token: Token![struct], + name: syn::Ident, + #[allow(dead_code)] + colon_token: Token![:], + repr: syn::Type, + flags: Flags, +} + +// impl $BitFlags:ident: $T:ty { +// $( +// $(#[$inner:ident $($args:tt)*])* +// const $Flag:ident = $value:expr; +// )+ +// } +#[derive(Debug)] +pub struct BitflagsImpl { + #[allow(dead_code)] + impl_token: Token![impl], + name: syn::Ident, + #[allow(dead_code)] + colon_token: Token![:], + repr: syn::Type, + flags: Flags, +} + +#[derive(Debug)] +pub enum Bitflags { + Struct(BitflagsStruct), + Impl(BitflagsImpl), +} + +impl Bitflags { + pub fn expand(&self) -> (Option<syn::ItemStruct>, syn::ItemImpl) { + match self { + Bitflags::Struct(BitflagsStruct { + attrs, + vis, + name, + repr, + flags, + .. + }) => { + let struct_ = parse_quote! { + #(#attrs)* + #vis struct #name { + bits: #repr, + } + }; + + let consts = flags.expand(name, repr, false); + let impl_ = parse_quote! { + impl #name { + #consts + } + }; + + (Some(struct_), impl_) + } + Bitflags::Impl(BitflagsImpl { + name, repr, flags, .. + }) => { + let consts = flags.expand(name, repr, true); + let impl_: syn::ItemImpl = parse_quote! { + impl #name { + #consts + } + }; + (None, impl_) + } + } + } +} + +impl Parse for Bitflags { + fn parse(input: ParseStream) -> ParseResult<Self> { + Ok(if input.peek(Token![impl]) { + Self::Impl(BitflagsImpl { + impl_token: input.parse()?, + name: input.parse()?, + colon_token: input.parse()?, + repr: input.parse()?, + flags: input.parse()?, + }) + } else { + Self::Struct(BitflagsStruct { + attrs: input.call(syn::Attribute::parse_outer)?, + vis: input.parse()?, + struct_token: input.parse()?, + name: input.parse()?, + colon_token: input.parse()?, + repr: input.parse()?, + flags: input.parse()?, + }) + }) + } +} + +// $(#[$inner:ident $($args:tt)*])* +// const $Flag:ident = $value:expr; +#[derive(Debug)] +struct Flag { + attrs: Vec<syn::Attribute>, + #[allow(dead_code)] + const_token: Token![const], + name: syn::Ident, + #[allow(dead_code)] + equals_token: Token![=], + value: syn::Expr, + #[allow(dead_code)] + semicolon_token: Token![;], +} + +struct FlagValueFold<'a> { + struct_name: &'a syn::Ident, + flag_names: &'a HashSet<String>, + out_of_line: bool, +} + +impl<'a> FlagValueFold<'a> { + fn is_self(&self, ident: &syn::Ident) -> bool { + ident == self.struct_name || ident == "Self" + } +} + +impl<'a> Fold for FlagValueFold<'a> { + fn fold_expr(&mut self, node: syn::Expr) -> syn::Expr { + // bitflags 2 doesn't expose `bits` publically anymore, and the documented way to + // combine flags is using the `bits` method, e.g. + // ``` + // bitflags! { + // struct Flags: u8 { + // const A = 1; + // const B = 1 << 1; + // const AB = Flags::A.bits() | Flags::B.bits(); + // } + // } + // ``` + // As we're transforming the struct definition into `struct StructName { bits: T }` + // as far as our bindings generation is concerned, `bits` is available as a field, + // so by replacing `StructName::FLAG.bits()` with `StructName::FLAG.bits`, we make + // e.g. `Flags::AB` available in the generated bindings. + // For out-of-line definitions of the struct(*), where the struct is defined as a + // newtype, we replace it with `StructName::FLAGS.0`. + // * definitions like: + // ``` + // struct Flags(u8); + // bitflags! { + // impl Flags: u8 { + // const A = 1; + // const B = 1 << 1; + // const AB = Flags::A.bits() | Flags::B.bits(); + // } + // } + // ``` + match node { + syn::Expr::MethodCall(syn::ExprMethodCall { + attrs, + receiver, + dot_token, + method, + args, + .. + }) if method == "bits" + && args.is_empty() + && matches!(&*receiver, + syn::Expr::Path(syn::ExprPath { path, .. }) + if path.segments.len() == 2 + && self.is_self(&path.segments.first().unwrap().ident) + && self + .flag_names + .contains(&path.segments.last().unwrap().ident.to_string())) => + { + return syn::Expr::Field(syn::ExprField { + attrs, + base: receiver, + dot_token, + member: if self.out_of_line { + syn::Member::Unnamed(parse_quote! {0}) + } else { + syn::Member::Named(method) + }, + }); + } + _ => {} + } + syn::fold::fold_expr(self, node) + } +} + +impl Flag { + fn expand( + &self, + struct_name: &syn::Ident, + repr: &syn::Type, + flag_names: &HashSet<String>, + out_of_line: bool, + ) -> TokenStream { + let Flag { + ref attrs, + ref name, + ref value, + .. + } = *self; + let folded_value = FlagValueFold { + struct_name, + flag_names, + out_of_line, + } + .fold_expr(value.clone()); + let value = if out_of_line { + quote! { ((#folded_value) as #repr) } + } else { + quote! { { bits: (#folded_value) as #repr } } + }; + quote! { + #(#attrs)* + pub const #name : #struct_name = #struct_name #value; + } + } +} + +impl Parse for Flag { + fn parse(input: ParseStream) -> ParseResult<Self> { + Ok(Self { + attrs: input.call(syn::Attribute::parse_outer)?, + const_token: input.parse()?, + name: input.parse()?, + equals_token: input.parse()?, + value: input.parse()?, + semicolon_token: input.parse()?, + }) + } +} + +#[derive(Debug)] +struct Flags(Vec<Flag>); + +impl Parse for Flags { + fn parse(input: ParseStream) -> ParseResult<Self> { + let content; + let _ = braced!(content in input); + let mut flags = vec![]; + while !content.is_empty() { + flags.push(content.parse()?); + } + Ok(Flags(flags)) + } +} + +impl Flags { + fn expand(&self, struct_name: &syn::Ident, repr: &syn::Type, out_of_line: bool) -> TokenStream { + let mut ts = quote! {}; + let flag_names = self + .0 + .iter() + .map(|flag| flag.name.to_string()) + .collect::<HashSet<_>>(); + for flag in &self.0 { + ts.extend(flag.expand(struct_name, repr, &flag_names, out_of_line)); + } + ts + } +} + +pub fn parse(tokens: TokenStream) -> ParseResult<Bitflags> { + let parser = Bitflags::parse; + parser.parse2(tokens) +} |