proc-macro-workshop:seq-4

godme發表於2022-07-05
// One of the big things callers will want to do with the sequential indices N
// is use them as part of an identifier, like f0 f1 f2 etc.
//
// Implement some logic to paste together any Ident followed by `~` followed by
// our loop variable into a single concatenated identifier.
//
// The invocation below will expand to:
//
//     fn f1() -> u64 { 1 * 2 }
//     fn f2() -> u64 { 2 * 2 }
//     fn f3() -> u64 { 3 * 2 }
//
// Optionally, also support more flexible arrangements like `f~N~_suffix` ->
// f0_suffix f1_suffix etc, though the test suite only requires `prefix~N` so
// you will need to add your own tests for this feature.
//
//
// Resources:
//
//     - Example of creating a new Ident from a string:
//       https://docs.rs/syn/1.0/syn/struct.Ident.html

use seq::seq;

seq!(N in 1..4 {
    fn f~N () -> u64 {
        N * 2
    }
});

// This f0 is written separately to detect whether your macro correctly starts
// with the first iteration at N=1 as specified in the invocation. If the macro
// incorrectly started at N=0 like in the previous tests cases, the first
// generated function would conflict with this one and the program would not
// compile.
fn f0() -> u64 {
    100
}

fn main() {
    let sum = f0() + f1() + f2() + f3();

    assert_eq!(sum, 100 + 2 + 4 + 6);
}

這裡要解析的是?~N的形式,我們需要預讀一些token
然後將數值帶入,把?~N轉換為?n的形式。

// solution4.rs
impl crate::parser::SeqParser {
    pub(crate) fn process_prefix(
        &self,
        idx: &mut usize,
        n: usize,
        prefix: &syn::Ident,
        buf: &Vec<proc_macro2::TokenTree>,
    ) -> std::option::Option<proc_macro2::TokenStream> {
        // 還有解析的空間
        if *idx + 2 < buf.len() {
            if let proc_macro2::TokenTree::Punct(p) = &buf[*idx + 1] {
                // 核對識別符號
                if p.as_char() == '~' {
                    if let proc_macro2::TokenTree::Ident(ident) = &buf[*idx + 2] {
                        // 核對迴圈變數,並且需要緊密聯絡
                        if ident == &self.variable_ident
                            && prefix.span().end() == p.span().start()
                            && p.span().end() == ident.span().start()
                        {
                            // 新生成識別符號進行替換
                            let combine_ident_litral = format!("{}{}", prefix.to_string(), n);
                            let combine_ident =
                                syn::Ident::new(&combine_ident_litral, prefix.span());
                            *idx += 3;
                            return std::option::Option::Some(quote::quote! {
                                #combine_ident
                            });
                        }
                    }
                }
            }
        }
        std::option::Option::None
    }
}

不過,這裡還算是原有邏輯的一個補充,因此,需要在seq-3中進行新增,也就是註釋部分。

整體邏輯保持不變

#[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 協議》,轉載必須註明作者和本文連結