Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Question about parsing args passed to an attribute like macro #86

Closed
SamWhited opened this issue Jan 27, 2017 · 4 comments
Closed

Question about parsing args passed to an attribute like macro #86

SamWhited opened this issue Jan 27, 2017 · 4 comments
Labels

Comments

@SamWhited
Copy link

SamWhited commented Jan 27, 2017

If you get stuck with Macros 1.1 I am happy to provide help even if the issue is not related to syn. Please file a ticket in this repo.

Thanks!

Is there a similar crate to syn but for parsing arguments in an attribute like proc macro? Eg. if I write a macro that looks something like:

#[proc_macro_attribute]
pub fn buildmenu(args: TokenStream, input: TokenStream) -> TokenStream { input }

which is used like:

#[buildmenu(file="gtk/menu.xml")]struct MainMenu;

how can I parse the "file=…" since there's no TokenStream API yet?

@SamWhited SamWhited changed the title Question about parsing the Question about parsing args passed to an attribute like macro Jan 27, 2017
@dtolnay
Copy link
Owner

dtolnay commented Jan 27, 2017

As far as I know there is nothing for this currently. We have only recently started thinking about how to support non-derive procedural macro use cases, for example in #81.

I think the easiest way to make this work right now is to turn the args into a fake derive input:

#[proc_macro_attribute]
pub fn buildmenu(args: TokenStream, input: TokenStream) -> TokenStream {
    let derive_input = format!("#[buildmenu{}] struct Dummy;", args);
    let attrs = syn::parse_derive_input(&derive_input).unwrap().attrs;
    let args = attrs.into_iter().next().unwrap().value;
    println!("{:#?}", args);
    input
}

This gives you args which is of type syn::MetaItem and is a syntax tree representation of #[buildmenu(...)]. In your case it would give you:

List(
    Ident(
        "buildmenu"
    ),
    [
        MetaItem(
            NameValue(
                Ident(
                    "file"
                ),
                Str(
                    "gtk/menu.xml",
                    Cooked
                )
            )
        )
    ]
)

Then actually doing something useful with that structure is a bit of a pain. I am tracking better ways of doing that in #25. My current thinking would be to build it on top of Serde, so you would have something like this:

#[derive(Deserialize)]
struct BuildmenuArgs {
    file: String
}

#[proc_macro_attribute]
pub fn buildmenu(args: TokenStream, input: TokenStream) -> TokenStream {
    let args: BuildmenuArgs = syn::load_args(&args).unwrap();
    /* ... */
}

Let me know what you think!

@SamWhited
Copy link
Author

SamWhited commented Jan 27, 2017

My current thinking would be to build it on top of Serde

That's an interesting thought; I love the idea of treating a token stream as a serialization format. It would also be nice to have some form of Iter like API on top of token stream's, so that a transducer or adapter like pattern could be used to transform the token stream. Eg.

#[proc_macro_attribute]
pub fn buildmenu(args: TokenStream, input: TokenStream) -> TokenStream {
    let args: BuildmenuArgs = syn::load_args(&args).unwrap();
    
    // Return some modified input (I couldn't think of a real example off the top of my head):
    args.Iter().Map(|token| -> Token {}).Remove(matchingtoken)
}

Thanks for your help!

@dtolnay
Copy link
Owner

dtolnay commented Jan 27, 2017

Yes! The TokenStream API is going to be fleshed out some more as follow-ups to the initial proc macro RFC (rust-lang/rfcs#1566).

Currently, macros implementing custom derive only have the option of converting the TokenStream to a string and converting a result string back to a TokenStream. This option will remain, but macro authors will also be able to operate directly on the TokenStream (which should be preferred, since it allows for hygiene and span support).

@SamWhited
Copy link
Author

SamWhited commented Jan 28, 2017

For the sake of completing this thread with a solution, I ended up going ahead and using Serde. Since file="somefile" basically looks like TOML, I just decoded it that way (trimming off the extra tokens first):

#[derive(Deserialize, Debug)]
struct BuildmenuArgs {
    file: String,
}

#[proc_macro_attribute]
pub fn buildmenu(args: TokenStream, input: TokenStream) -> TokenStream {
    let args_string = args.to_string();
    let toml_str = args_string.trim_matches(|c| c == ')' || c == '(').trim();
    let buildmenuargs: BuildmenuArgs = toml::decode_str(toml_str).unwrap();

    input
}

Thanks for the idea!

@dtolnay dtolnay closed this as completed Feb 19, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants