Вам нужен макрос процедурного атрибута и немного работы. Пример реализации находится на Github ;имейте в виду, что он довольно грубый по краям, но хорошо работает с самого начала.
Цель состоит в том, чтобы:
#[derivefields(u32, "field", 3)]
struct MyStruct {
foo: u32
}
перейти к:
struct MyStruct {
pub field_0: u32,
pub field_1: u32,
pub field_2: u32,
foo: u32
}
Для этого, во-первых, мы собираемся установить пару вещей. Нам понадобится struct
, чтобы легко хранить и извлекать наши аргументы:
struct MacroInput {
pub field_type: syn::Type,
pub field_name: String,
pub field_count: u64
}
Остальное - конвейер:
impl Parse for MacroInput {
fn parse(input: ParseStream) -> syn::Result<Self> {
let field_type = input.parse::<syn::Type>()?;
let _comma = input.parse::<syn::token::Comma>()?;
let field_name = input.parse::<syn::LitStr>()?;
let _comma = input.parse::<syn::token::Comma>()?;
let count = input.parse::<syn::LitInt>()?;
Ok(MacroInput {
field_type: field_type,
field_name: field_name.value(),
field_count: count.base10_parse().unwrap()
})
}
}
Это определяет syn::Parse
в нашем struct
и позволяет нам использовать syn::parse_macro_input!()
для простого анализа наших аргументов.
#[proc_macro_attribute]
pub fn derivefields(attr: TokenStream, item: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(attr as MacroInput);
let mut found_struct = false; // We actually need a struct
item.into_iter().map(|r| {
match &r {
&proc_macro::TokenTree::Ident(ref ident) if ident.to_string() == "struct" => { // react on keyword "struct" so we don't randomly modify non-structs
found_struct = true;
r
},
&proc_macro::TokenTree::Group(ref group) if group.delimiter() == proc_macro::Delimiter::Brace && found_struct == true => { // Opening brackets for the struct
let mut stream = proc_macro::TokenStream::new();
stream.extend((0..input.field_count).fold(vec![], |mut state:Vec<proc_macro::TokenStream>, i| {
let field_name_str = format!("{}_{}", input.field_name, i);
let field_name = Ident::new(&field_name_str, Span::call_site());
let field_type = input.field_type.clone();
state.push(quote!(pub #field_name: #field_type,
).into());
state
}).into_iter());
stream.extend(group.stream());
proc_macro::TokenTree::Group(
proc_macro::Group::new(
proc_macro::Delimiter::Brace,
stream
)
)
}
_ => r
}
}).collect()
}
Поведение модификатора создает новый TokenStream
и добавляет наши поля first . Это чрезвычайно важно ;предположим, что указанная структура struct Foo { bar: u8 }
;добавление last вызовет ошибку разбора из-за отсутствия ,
. Предварительное добавление позволяет нам не заботиться об этом, поскольку завершающая запятая в структуре не является ошибкой разбора.
Как только мы получим это TokenStream
, мы последовательно extend()
будем его генерировать с токенами из quote::quote!()
;это позволяет нам не создавать фрагменты токенов самостоятельно. Одна ошибка заключается в том, что имя поля необходимо преобразовать в Ident
(в противном случае оно указывается в кавычках, а это не то, что нам нужно).
Затем мы возвращаем этот измененный TokenStream
как TokenTree::Group
чтобы показать, что это действительно блок, разделенный скобками.
При этом мы также решили несколько проблем:
- Так как структуры без именованных элементов (например,
pub struct Foo(u32)
)на самом деле никогда не имеет открывающей скобки, этот макрос не предназначен для этого - Он не будет запрещать любой элемент, не являющийся структурой
- Он также не будет содержать структуры безучастник