Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.0k views
in Technique[技术] by (71.8m points)

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

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)?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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() {}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...