是否可以修改宏内部令牌的大小写?

时间:2017-08-06 15:43:13

标签: macros rust

我正在编写一个宏来创建一个管理用户输入的结构。我正在使用包bitflagssdl2返回是键返回的示例。

此宏列出所有可能的输入,然后

($($flag:ident = $value:expr;)+)  => { ... }
  1. 使用输入名称

    创建一个新的位标志
    bitflags!(
        struct KeyType: u64 {
        $(
            const $flag = $value;// UPPER_CASE is the norm for globals: 'RETURN'
        )+
        }
    );
    
  2. 使用Keycode枚举检查是否按下了键。

    match event {
        $(Event::KeyDown { keycode: Some(Keycode::$flag), .. } => { 
            self.flags.insert($flag); 
        },)+
         _ => ()
    }// All enum fields start with a capital letter: 'Return'
    
  3. 创建一个getter函数:

    $(
        pub fn $flag(&self) -> bool { // lower_case is the norm for functions: 'return'
            self.flags.contains($flag)
        }
    )+
    
  4. 我可以调整$flag以满足所有三个要求(flagFlagFLAG)吗?

2 个答案:

答案 0 :(得分:3)

Macro_rules宏无法执行此操作。您可能需要将其实现为程序宏,该宏可以执行任意Rust代码以生成扩展代码。特别是,程序宏可以调用str::to_uppercasestr::to_lowercase

// [dependencies]
// quote = "0.6"
// syn = "0.15"

extern crate proc_macro;
use self::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;
                )*
            }
        );

        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)
                }
            )*
        }
    })
}

使用宏:

use sleeping_panda::sleeping_panda;

sleeping_panda! {
    Backspace = 8;
    Tab = 9;
}

fn main() {}

答案 1 :(得分:3)

paste板条箱有助于在macro_rules宏中进行标识符大小写转换,使用[<$ident:lower>]表示小写,[<$ident:upper>]表示大写。

类似这样的东西:

// [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;
}