问题
I am writing a macro which creates a struct managing user input. I am using the crates bitflags and sdl2. Return is an example for the key Return.
This macro takes a list of all possible inputs and then
($($flag:ident = $value:expr;)+) => { ... }
Creates a new bitflag with the name of the input
bitflags!( struct KeyType: u64 { $( const $flag = $value;// UPPER_CASE is the norm for globals: 'RETURN' )+ } );
Checks if the key is pressed, using the Keycode enum.
match event { $(Event::KeyDown { keycode: Some(Keycode::$flag), .. } => { self.flags.insert($flag); },)+ _ => () }// All enum fields start with a capital letter: 'Return'
Creates a getter function:
$( pub fn $flag(&self) -> bool { // lower_case is the norm for functions: 'return' self.flags.contains($flag) } )+
Can I adapt $flag
to fit all three requirements (flag
, Flag
, FLAG
)?
回答1:
Macro_rules macros cannot do this. You would need to implement it as a procedural macro, which is allowed to perform arbitrary Rust code to generate the expanded code. In particular, procedural macros are able to call str::to_uppercase and str::to_lowercase.
// [dependencies]
// quote = "1.0"
// syn = "1.0"
use proc_macro::TokenStream;
use quote::quote;
use syn::parse::{Parse, ParseStream, Result};
use syn::{parse_macro_input, Expr, Ident, Token};
struct Input {
flags: Vec<Ident>,
values: Vec<Expr>,
}
// $( $flag:ident = $value:expr; )*
impl Parse for Input {
fn parse(input: ParseStream) -> Result<Self> {
let mut flags = Vec::new();
let mut values = Vec::new();
while !input.is_empty() {
flags.push(input.parse()?);
input.parse::<Token![=]>()?;
values.push(input.parse()?);
input.parse::<Token![;]>()?;
}
Ok(Input { flags, values })
}
}
#[proc_macro]
pub fn sleeping_panda(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as Input);
let camelcase_flags = &input.flags; // assume CamelCase in the input
let bitflag_values = &input.values;
let uppercase_flags = input
.flags
.iter()
.map(|ident| Ident::new(&ident.to_string().to_uppercase(), ident.span()));
// Some copies because these need to appear multiple times in the generated code.
let uppercase_flags2 = uppercase_flags.clone();
let uppercase_flags3 = uppercase_flags.clone();
let lowercase_flags = input
.flags
.iter()
.map(|ident| Ident::new(&ident.to_string().to_lowercase(), ident.span()));
TokenStream::from(quote! {
bitflags::bitflags! (
struct KeyType: u64 {
#(
const #uppercase_flags = #bitflag_values;
)*
}
);
pub struct SleepingPanda {
flags: KeyType,
}
impl SleepingPanda {
pub fn receive_event(&mut self, event: sdl2::event::Event) {
match event {
#(
sdl2::event::Event::KeyDown {
keycode: Some(sdl2::keyboard::Keycode::#camelcase_flags),
..
} => {
self.flags.insert(KeyType::#uppercase_flags2);
}
)*
_ => {}
}
}
#(
pub fn #lowercase_flags(&self) -> bool {
self.flags.contains(KeyType::#uppercase_flags3)
}
)*
}
})
}
Using the macro:
use sleeping_panda::sleeping_panda;
sleeping_panda! {
Backspace = 8;
Tab = 9;
}
fn main() {}
回答2:
The paste crate helps do identifier case conversions in a macro_rules macro, using [<$ident:lower>]
for lowercase and [<$ident:upper>]
for uppercase.
Something like this:
// [dependencies]
// paste = "0.1"
macro_rules! sleeping_panda {
($($flag:ident = $value:expr;)+) => {
paste::item! {
bitflags::bitflags! (
struct KeyType: u64 {
$(
const [<$flag:upper>] = $value;
)*
}
);
pub struct SleepingPanda {
flags: KeyType,
}
impl SleepingPanda {
pub fn receive_event(&mut self, event: sdl2::event::Event) {
match event {
$(
sdl2::event::Event::KeyDown {
keycode: Some(sdl2::keyboard::Keycode::$flag),
..
} => {
self.flags.insert(KeyType::[<$flag:upper>]);
}
)*
_ => {}
}
}
$(
pub fn [<$flag:lower>](&self) -> bool {
self.flags.contains(KeyType::[<$flag:upper>])
}
)*
}
}
};
}
sleeping_panda! {
Backspace = 8;
Tab = 9;
}
来源:https://stackoverflow.com/questions/45533702/is-it-possible-to-modify-the-case-of-a-token-inside-of-a-macro