proc-macro-workshop:seq-3

godme發表於2022-07-05
// 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 協議》,轉載必須註明作者和本文連結