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 协议》,转载必须注明作者和本文链接
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!