proc-macro-workshop:seq-5
审题
// So far our macro has repeated the entire loop body. This is not sufficient
// for some use cases because there are restrictions on the syntactic position
// that macro invocations can appear in. For example the Rust grammar would not
// allow a caller to write:
//
// enum Interrupt {
// seq!(N in 0..16 {
// Irq~N,
// });
// }
//
// because this is just not a legal place to put a macro call.
//
// Instead we will implement a way for the caller to designate a specific part
// of the macro input to be repeated, so that anything outside that part does
// not get repeated. The repeated part will be written surrounded by #(...)*.
//
// The invocation below should expand to:
//
// #[derive(Copy, Clone, PartialEq, Debug)]
// enum Interrupt {
// Irq0,
// ...
// Irq15,
// }
//
// Optionally, allow for there to be multiple separate #(...)* sections,
// although the test suite does not exercise this case. The #(...)* sections
// will each need to be repeated according to the same loop bounds.
use seq::seq;
seq!(N in 0..16 {
#[derive(Copy, Clone, PartialEq, Debug)]
enum Interrupt {
#(
Irq~N,
)*
}
});
fn main() {
let interrupt = Interrupt::Irq8;
assert_eq!(interrupt as u8, 8);
assert_eq!(interrupt, Interrupt::Irq8);
}
在这里,我们追寻到了熟悉的味道#(?)*
。
曾经在builder
和debug
我们大量使用它,现在我们需要去实现它。
主要是以#
开始的情况下,接下来解析(?)
,并且以*
结尾的情况下。
将?
内部的逻辑,用seq-4中的逻辑去实现。
题解
impl crate::parser::SeqParser {
pub(crate) fn expand_section(&self) -> std::option::Option<proc_macro2::TokenStream> {
let buffer = syn::buffer::TokenBuffer::new2(self.body.clone());
let (expended, expend_section_stream) = self.do_expand_section(buffer.begin());
if expended {
return std::option::Option::Some(expend_section_stream);
}
std::option::Option::None
}
pub(crate) fn do_expand_section(
&self,
origin_cursor: syn::buffer::Cursor,
) -> (bool, proc_macro2::TokenStream) {
let mut found = false;
let mut res = proc_macro2::TokenStream::new();
// 因为存在# (?) *的解析情况,直接使用数组的形式解析不够连贯
// 这里采用cursor会更加丝滑
// 不便的就是处理的项目需要更加具体的枚举出来
let mut cursor = origin_cursor;
while !cursor.eof() {
// 符号检测,主要答题逻辑
if let Some((prefix, prefix_next_cursor)) = cursor.punct() {
if prefix.as_char() == '#' {
if let Some((group_cursor, _, group_next_cursor)) =
prefix_next_cursor.group(proc_macro2::Delimiter::Parenthesis)
{
if let Some((suffix, suffix_next_cursor)) = group_next_cursor.punct() {
if suffix.as_char() == '*' {
for i in self.begin..self.end {
// 匹配部分,使用之前的方式进行展开
let t = self.do_expand_repeat(&group_cursor.token_stream(), i);
res.extend(t);
}
cursor = suffix_next_cursor;
found = true;
continue;
}
}
}
}
}
// {},递归展开
if let Some((group_cursor, _, group_next_cursor)) =
cursor.group(proc_macro2::Delimiter::Brace)
{
let (sub_found, sub_stream) = self.do_expand_section(group_cursor);
found = sub_found;
// 注意{}还原
res.extend(quote::quote!({#sub_stream}));
cursor = group_next_cursor;
continue;
// [],递归展开
} else if let Some((group_cursor, _, group_next_cursor)) =
cursor.group(proc_macro2::Delimiter::Bracket)
{
let (sub_found, sub_stream) = self.do_expand_section(group_cursor);
found = sub_found;
// [] 还原
res.extend(quote::quote!([#sub_stream]));
cursor = group_next_cursor;
continue;
// (),递归展开
} else if let Some((group_cursor, _, group_next_cursor)) =
cursor.group(proc_macro2::Delimiter::Parenthesis)
{
let (sub_found, sub_stream) = self.do_expand_section(group_cursor);
found = sub_found;
// ()还原
res.extend(quote::quote!((#sub_stream)));
cursor = group_next_cursor;
continue;
// 其他情况直接添加,不过枚举匹配麻烦
} else if let Some((punct, next)) = cursor.punct() {
res.extend(quote::quote!(#punct));
cursor = next;
continue;
} else if let Some((ident, next)) = cursor.ident() {
res.extend(quote::quote!(#ident));
cursor = next;
continue;
} else if let Some((literal, next)) = cursor.literal() {
res.extend(quote::quote!(#literal));
cursor = next;
continue;
} else if let Some((lifetime, next)) = cursor.lifetime() {
res.extend(quote::quote!(#lifetime));
cursor = next;
continue;
}
}
(found, res)
}
}
在这里,有一个容易疏忽的点,那就是不一定存在#()*
的结构。
因此我们需要返回一个found
的标记,因为针对section
的展开兼容了repeat
。
但是repeat
不兼容section
,因此,如果section
展开了,就可以直接返回。
反之,进行repeat
展开。
结果
#[proc_macro]
pub fn seq(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let parser = syn::parse_macro_input!(input as crate::parser::SeqParser);
if let std::option::Option::Some(repeat_section_stream) = parser.expand_section() {
return repeat_section_stream.into();
}
return parser.expend_repeat().into();
}
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: