#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
use proc_macro2::{Spacing, TokenStream, TokenTree};
use quote::quote;
use syn::{ItemTrait, parse_macro_input};
mod apply;
mod apply_tuple;
mod batch_trait_entry;
mod codegen;
mod diagnostic;
mod generic;
mod parse;
mod parse_atom;
mod path_prefix;
mod preprocess;
mod preprocess_helpers;
mod scan;
mod types;
mod types_render;
use batch_trait_entry::parse_batch_trait_entry;
use diagnostic::compile_error_str;
use scan::Cursor;
use types::{Op, reset_fresh_counter};
#[proc_macro_attribute]
pub fn batch_impl(
attr: proc_macro::TokenStream,
item: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
expand_attr_macro(attr, item, true)
}
#[proc_macro_attribute]
pub fn batch_impl_only(
attr: proc_macro::TokenStream,
item: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
expand_attr_macro(attr, item, false)
}
fn expand_attr_macro(
attr: proc_macro::TokenStream,
item: proc_macro::TokenStream,
include_trait: bool,
) -> proc_macro::TokenStream {
reset_fresh_counter();
let trait_item = parse_macro_input!(item as ItemTrait);
let trait_name = trait_item.ident.clone();
let attr_vec = TokenStream::from(attr).into_iter().collect::<Vec<_>>();
let (trait_full_path, trait_last_ident, rest_tokens) =
if !include_trait {
match path_prefix::try_parse_path_prefix(&attr_vec) {
Some((path, last_ident, rest)) => {
match last_ident {
Some(id) if id == trait_name => {
let path_ts: TokenStream =
path.into_iter().collect();
(path_ts, trait_name.clone(), rest)
},
Some(id) => {
let msg = format!(
"batch-impl: 路径前缀 `#...{}` \
的末尾标识符与 trait 名 `{}` \
不一致;二者必须相同",
id, trait_name,
);
return compile_error_str(&msg).into();
},
None => {
let msg = "batch-impl: 路径前缀 `#` 后 \
期望至少一个标识符作为 trait 路径";
return compile_error_str(msg).into();
},
}
},
None => {
let ts = quote![#trait_name];
(ts, trait_name.clone(), attr_vec.clone())
},
}
} else {
let ts = quote![#trait_name];
(ts, trait_name.clone(), attr_vec.clone())
};
let mut cursor = Cursor::new(&rest_tokens);
let expanded =
match preprocess::expand_tokens(&mut cursor, &trait_item) {
Ok(tokens) => tokens,
Err(err) => return err.into(),
};
cursor = Cursor::new(&expanded);
let is_unsafe = trait_item.unsafety.is_some();
let start_trait = if include_trait {
Some(trait_item)
} else {
None
};
let impls = parse_batch_trait_entry(
&mut cursor,
Op::Comma,
&trait_full_path,
&trait_last_ident,
is_unsafe,
start_trait,
);
impls.into()
}
#[proc_macro]
pub fn batch_trait(
input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
reset_fresh_counter();
let tokens = TokenStream::from(input).into_iter().collect::<Vec<_>>();
let mut cursor = Cursor::new(&tokens);
let mut result = quote![];
loop {
while cursor.is_punct(';') {
cursor.bump();
}
if cursor.at_end() {
break;
}
let is_unsafe = if matches!(cursor.peek(), Some(TokenTree::Ident(id)) if *id == "unsafe")
{
cursor.bump();
true
} else {
false
};
let path_start = cursor.pos();
let mut depth = 0i32;
while let Some(token) = cursor.peek() {
match token {
TokenTree::Punct(p) if p.as_char() == '<' => {
depth += 1;
cursor.bump();
},
TokenTree::Punct(p) if p.as_char() == '>' => {
depth -= 1;
cursor.bump();
},
TokenTree::Punct(p)
if p.as_char() == ':' && depth == 0 =>
{
if matches!(cursor.peek_at(1), Some(TokenTree::Punct(p2)) if p.spacing()==Spacing::Joint && p2.as_char() == ':')
{
cursor.bump();
cursor.bump();
} else {
break;
}
},
_ => cursor.bump(),
}
}
let trait_path = cursor.slice_since(path_start);
if trait_path.is_empty() {
result.extend(compile_error_str(
"batch_trait! 中期望 trait 名称",
));
break;
}
let trait_full_path = trait_path.iter().cloned().collect();
let trait_last_ident = match trait_path
.iter()
.filter_map(|tt| {
if let TokenTree::Ident(id) = tt {
Some(id)
} else {
None
}
})
.next_back()
{
Some(ident) => ident,
None => {
result.extend(compile_error_str(
"batch_trait! 中期望标识符作为 trait 名称",
));
break;
},
};
if !cursor.is_punct(':') {
result.extend(compile_error_str(
"batch_trait! 中期望 ':' 分隔 trait 名称和 impl-specs",
));
break;
}
cursor.bump();
let impl_code = parse_batch_trait_entry(
&mut cursor,
Op::Semi,
&trait_full_path,
trait_last_ident,
is_unsafe,
None,
);
result.extend(impl_code);
}
result.into()
}