1use proc_macro::TokenStream;
2use proc_macro2::{Ident, TokenStream as TokenStream2, TokenTree};
3use quote::quote;
4use syn::{parse_macro_input, ItemTrait};
5
6mod types;
7mod apply;
8mod parse;
9mod codegen;
10
11use codegen::generate_impl;
12use parse::{parse_item, Cursor};
13use types::{reset_fresh_counter, Op};
14
15#[proc_macro_attribute]
44pub fn batch_impl(attr: TokenStream, item: TokenStream) -> TokenStream {
45 reset_fresh_counter();
46 let trait_item = parse_macro_input!(item as ItemTrait);
47 let trait_name = trait_item.ident.clone();
48 let attr_vec = TokenStream2::from(attr).into_iter().collect::<Vec<_>>();
49 let trait_name_ts: TokenStream2 = quote![#trait_name];
50 let mut cursor = Cursor::new(&attr_vec);
51 let impls = parse_batch_trait_entry(
52 &mut cursor, Op::Comma, &trait_name_ts, &trait_name,
53 trait_item.unsafety.is_some(), Some(trait_item),
54 );
55 impls.into()
56}
57
58#[proc_macro]
78pub fn batch_trait(input: TokenStream) -> TokenStream {
79 reset_fresh_counter();
80 let tokens = TokenStream2::from(input).into_iter().collect::<Vec<_>>();
81 let mut cursor = Cursor::new(&tokens);
82 let mut result = quote![];
83 loop {
84 while cursor.is_punct(';') {
86 cursor.bump();
87 }
88 if cursor.at_end() {
89 break;
90 }
91
92 let is_unsafe = if matches!(cursor.peek(), Some(TokenTree::Ident(id)) if *id == "unsafe") {
94 cursor.bump();
95 true
96 } else {
97 false
98 };
99
100 let path_start = cursor.pos();
102 let mut depth = 0i32;
103 while let Some(token) = cursor.peek() {
104 match token {
105 TokenTree::Punct(p) if p.as_char() == '<' => {
106 depth += 1;
107 cursor.bump();
108 }
109 TokenTree::Punct(p) if p.as_char() == '>' => {
110 depth -= 1;
111 cursor.bump();
112 }
113 TokenTree::Punct(p) if p.as_char() == ':' && depth == 0 => {
114 if matches!(cursor.peek_at(1), Some(TokenTree::Punct(p2)) if p2.as_char() == ':') {
115 cursor.bump();
116 cursor.bump();
117 } else {
118 break;
119 }
120 }
121 _ => cursor.bump(),
122 }
123 }
124 let trait_path = cursor.slice_since(path_start);
125 if trait_path.is_empty() {
126 result.extend(generate_compile_error("batch_trait! 中期望 trait 名称"));
127 break;
128 }
129 let trait_full_path = match extract_trait_path(trait_path) {
130 Ok(path) => path,
131 Err(e) => { result.extend(e); break; }
132 };
133 let trait_last_ident = match extract_last_ident(trait_path) {
134 Ok(ident) => ident,
135 Err(e) => { result.extend(e); break; }
136 };
137 if !cursor.is_punct(':') {
138 result.extend(generate_compile_error(
139 "batch_trait! 中期望 ':' 分隔 trait 名称和 impl-specs",
140 ));
141 break;
142 }
143 cursor.bump();
144 let impl_code = parse_batch_trait_entry(
145 &mut cursor, Op::Semi, &trait_full_path,
146 trait_last_ident, is_unsafe, None,
147 );
148 result.extend(impl_code);
149 }
150 result.into()
151}
152
153fn parse_batch_trait_entry(
162 cursor: &mut Cursor,
163 top_level: Op,
164 trait_full_path: &TokenStream2,
165 trait_last_ident: &Ident,
166 is_unsafe_trait: bool,
167 start_trait: Option<ItemTrait>,
168) -> TokenStream2 {
169 let mut tys = vec![];
170 while let Some(ty) = parse_item(cursor, top_level, Some(trait_last_ident)) {
171 let mut queue = vec![ty];
172 while let Some(item) = queue.pop() {
173 match item.expand() {
174 Ok(expanded) => {
175 for e in expanded.into_iter().rev() {
176 queue.push(e);
177 }
178 }
179 Err(leaf) => tys.push(leaf),
180 }
181 }
182 }
183 let mut impls = start_trait.map_or(quote![], |t| quote![#t]);
184 for t in tys {
185 impls.extend(generate_impl(t, trait_full_path, is_unsafe_trait));
186 }
187 impls
188}
189
190fn extract_trait_path(trait_path: &[TokenTree]) -> Result<TokenStream2, TokenStream2> {
192 let path: TokenStream2 = trait_path.iter().cloned().collect();
193 if path.is_empty() {
194 Err(generate_compile_error("batch_trait! 中期望 trait 名称"))
195 } else {
196 Ok(path)
197 }
198}
199
200fn extract_last_ident(trait_path: &[TokenTree]) -> Result<&Ident, TokenStream2> {
202 trait_path
203 .iter()
204 .filter_map(|tt| if let TokenTree::Ident(id) = tt { Some(id) } else { None })
205 .next_back()
206 .ok_or_else(|| generate_compile_error("batch_trait! 中期望标识符作为 trait 名称"))
207}
208
209fn generate_compile_error(msg: &str) -> TokenStream2 {
211 quote! { compile_error!(#msg); }
212}