Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/oxidecomputer/serde_tokenstream
serde::Deserializer for proc_macro/proc_macro2::TokenStream
https://github.com/oxidecomputer/serde_tokenstream
Last synced: about 2 months ago
JSON representation
serde::Deserializer for proc_macro/proc_macro2::TokenStream
- Host: GitHub
- URL: https://github.com/oxidecomputer/serde_tokenstream
- Owner: oxidecomputer
- License: apache-2.0
- Created: 2020-05-14T07:53:39.000Z (over 4 years ago)
- Default Branch: main
- Last Pushed: 2024-07-24T03:29:42.000Z (about 2 months ago)
- Last Synced: 2024-07-24T05:17:29.531Z (about 2 months ago)
- Language: Rust
- Homepage:
- Size: 187 KB
- Stars: 62
- Watchers: 22
- Forks: 4
- Open Issues: 8
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# `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",
);
```