Is it possible to modify the case of a token inside of a macro?

前提是你 提交于 2021-02-15 06:01:20

问题


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;)+)  => { ... }
  1. 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'
        )+
        }
    );
    
  2. 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'
    
  3. 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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!