proc-macro-workshop:seq-5

godme發表於2022-07-05
// 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);
}

在這裡,我們追尋到了熟悉的味道#(?)*
曾經在builderdebug我們大量使用它,現在我們需要去實現它。
主要是以#開始的情況下,接下來解析(?),並且以*結尾的情況下。
?內部的邏輯,用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 協議》,轉載必須註明作者和本文連結