# `serde_tokenstream` This Rust crate is intended for use with macros that need bespoke configuration. It's implemented as a `serde::Deserializer` that operates on a `proc_macro2::TokenSteam` (easily converted from the standard `proc_macro::TokenStream`). ## Usage Say we're building an attribute proc macro that you want consumers to use like this: ```rust #[MyMacro { name = "SNPP", owner = "Canary M Burns", details = { kind = Fission, year_of_opening = 1968, } }] fn some_func() { ... } ``` It's also useful for function-like macros: ```rust my_macro!( name = "SNPP", owner = "Hans", layoffs_in_alphabetical_order = [ "Simpson, Homer" ] ); ``` The function that implements the proc macro must have two parameters (both of type `proc_macro::TokenStream`): attributes (the tokens with the braces that follow the name of the macro), and the item (the function, type, etc. to which the macro is applied): ```rust #[proc_macro_attribute] pub fn MyMacro( attr: proc_macro::TokenStream, item: proc_macro::TokenStream, ) -> proc_macro::TokenStream { ... } ``` We'll first define the `struct` type that represents the configuration and `derive` a `serde::Deserialize`: ```rust #[derive(Deserialize)] struct Config { name: String, owner: String, details: ConfigDetails, } #[derive(Deserialize)] struct ConfigDetails { kind: ConfigDetailsType, year_of_opening: usize, } #[derive(Deserialize)] enum ConfigDetailsType { Coal, Fission, Hydroelectric, } ``` Now we can parse `attr` into the `Config` struct with `serde_tokenstream::from_tokenstream`: ```rust use proc_macro2::TokenStream; use serde_tokenstream::from_tokenstream; #[allow(non_snake_case)] #[proc_macro_attribute] pub fn MyMacro( attr: proc_macro::TokenStream, item: proc_macro::TokenStream, ) -> proc_macro::TokenStream { let config = match from_tokenstream::(&TokenStream::from(attr)) { Ok(c) => c, Err(err) => return err.to_compile_error().into(), }; ... } ``` See the `serde` documentation for the full range of controls that can be applied to types and their members. ## Error Handling Errors indicate the problematic portion of consuming code to assist the macro consumer: ```rust #[MyMacro{ name = "Rocinante", owner = "Rocicorp", details = { kind = Fusion, year_of_opening = 2347 } }] fn deploy() { ... } ``` ``` error: unknown variant `Fusion`, expected one of `Coal`, `Fission`, `Hydroelectric` --> tests/test_err1.rs:7:16 | 7 | kind = Fusion, | ^^^^^^ ``` ## Nested attributes For parsing attributes nested inside an outer macro, use `from_tokenstream_spanned`. This function provides better span attribution for errors at the top level. The most common use is with `syn::MetaList`. For example, if your macro is a derive macro: ```rust #[derive(MyRobot)] #[robot { name = "Mawhrin-Skel", kind = Drone, planet = "Eä", }] fn monitor() { ... } ``` Then `robot` can be interpreted as a `syn::MetaList` instance. With that: ```rust use serde_tokenstream::from_tokenstream; #[derive(Deserialize)] struct Robot { ... } #[proc_macro_derive(MyRobot, attributes(robot))] pub fn my_robot(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let list = /* obtain the `syn::MetaList` from the input */; let config = match from_tokenstream_spanned::( list.delimiter.span(), &list.tokens ) { Ok(c) => c, Err(err) => return err.to_compile_error().into(), }; } ``` ## TokenStream and syn::\* values In some cases, it's useful to pass TokenStream values as parameters to a macro. In this case we can use the `TokenStreamWrapper` which is a wrapper around `TokenStream` that implements `Deserialize` or `ParseWrapper` which is a wrapper around `syn::Parse` that implements `Deserialize`. The latter is useful for passing in, for example, a `syn::Path`, or other specific entities from the `syn` crate. ## OrderedMap You may want to use the map syntax with keys that cannot be used by types such as `HashMap` or `BTreeMap` because they don't implement `Hash` or `Ord`. In those cases, you can use an `OrderedMap` and extract the pairs as an iterator of tuples. Let's say we we want our "keys" to be `serde_json::Value`s and our value to be... whatever... `String`s! You can't use `serde_json::Value` as the key in a `HashMap` or `BTreeMap`, but we can for an `OrderedMap`: ```rust let config = from_tokenstream::>(tokens)?; ``` The macro can then be invoked like this: ```rust my_macro!( { "type" = "string", "format" = "uuid", } = "uuid::Uuid", { "type" = "string", "format" = "ip", } = "std::net::IpAddr", ); ```