proc-macro-workshop:seq-3
审题
// Now construct the generated code! Produce the output TokenStream by repeating
// the loop body the correct number of times as specified by the loop bounds and
// replacing the specified identifier with the loop counter.
//
// The invocation below will need to expand to a TokenStream containing:
//
// compile_error!(concat!("error number ", stringify!(0)));
// compile_error!(concat!("error number ", stringify!(1)));
// compile_error!(concat!("error number ", stringify!(2)));
// compile_error!(concat!("error number ", stringify!(3)));
//
// This test is written as a compile_fail test because our macro isn't yet
// powerful enough to do anything useful. For example if we made it generate
// something like a function, every one of those functions would have the same
// name and the program would not compile.
use seq::seq;
seq!(N in 0..4 {
compile_error!(concat!("error number ", stringify!(N)));
});
fn main() {}
之前我们解析的时候,{}
内部是完全的按照rust
语法直接读取。
从用例(提示)上面可以看到,应该是需要将N
在后续的代码片段中替换为对应的数值。
参考错误提示
error: error number 0
--> tests/03-expand-four-errors.rs:20:5
|
20 | compile_error!(concat!("error number ", stringify!(N)));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: error number 1
--> tests/03-expand-four-errors.rs:20:5
|
20 | compile_error!(concat!("error number ", stringify!(N)));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: error number 2
--> tests/03-expand-four-errors.rs:20:5
|
20 | compile_error!(concat!("error number ", stringify!(N)));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: error number 3
--> tests/03-expand-four-errors.rs:20:5
|
20 | compile_error!(concat!("error number ", stringify!(N)));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
我们还需要精准的定位到错误span
。
题解
只要外部for
之后,找到对应的N
替换为当前的次数即可。
因为前面已经impl
了一个方法,后续题解都使用同样的方法,直接在结构体内定义。
impl crate::parser::SeqParser {
pub(crate) fn expend_repeat(&self) -> proc_macro2::TokenStream {
let mut res = proc_macro2::TokenStream::new();
// 外层循环,遍历语句并且替换N
for i in self.begin..self.end {
res.extend(self.do_expand_repeat(&self.body, i));
}
res
}
pub(crate) fn do_expand_repeat(
&self,
ts: &proc_macro2::TokenStream,
n: usize,
) -> proc_macro2::TokenStream {
let buf = &ts.clone().into_iter().collect::<Vec<_>>();
let mut res = proc_macro2::TokenStream::new();
let mut idx = 0usize;
while idx < buf.len() {
let node = &buf[idx];
match node {
// group
proc_macro2::TokenTree::Group(group) => {
let recurrence_expand_stream = self.do_expand_repeat(&group.stream(), n);
// 因为解析消耗了括号,我们应该补充回来
let mut wrap_in_group_stream =
proc_macro2::Group::new(group.delimiter(), recurrence_expand_stream);
// 注意span,否则报错位置对不上
wrap_in_group_stream.set_span(group.clone().span());
res.extend(quote::quote!(#wrap_in_group_stream));
}
// 标识符
proc_macro2::TokenTree::Ident(ident) => {
// 后续题解,当前无需关注
// if let std::option::Option::Some(token_stream) =
// self.process_prefix(&mut idx, n, ident, buf)
// {
// res.extend(token_stream);
// continue;
// }
// 如果是指定的标记,替换为当前的循环数值
// 因为生成代码,实际上是数字的字面值
if ident == &self.variable_ident {
let new_ident = proc_macro2::Literal::i64_unsuffixed(n as i64);
res.extend(quote::quote!(#new_ident));
} else { // 非循环标志,直接添加
res.extend(quote::quote!(#node));
}
}
_ => { // 其他情况直接添加
res.extend(quote::quote!(#node));
}
}
idx += 1;
}
res
}
}
这里利用了proc_macro2::TokenStream
转化为数组的能力,方便我们对节点的遍历。
目前,我们已经自己组装出了自定义的语法。
整体
#[proc_macro]
pub fn seq(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let parser = syn::parse_macro_input!(input as crate::parser::SeqParser);
return parser.expend_repeat().into();
}
因为是已经封装好了,直接调用方法即可。
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: