use proc_macro::{Delimiter, TokenStream, TokenTree};
#[proc_macro_attribute]
pub fn main(attribute: TokenStream, item: TokenStream) -> TokenStream {
match expand(attribute, item) {
Ok(stream) => stream,
Err(message) => error(&message),
}
}
fn error(message: &str) -> TokenStream {
format!("compile_error!({:?});", message)
.parse()
.expect("a compile_error! invocation is always valid Rust")
}
fn expand(attribute: TokenStream, item: TokenStream) -> Result<TokenStream, String> {
let backend = if attribute.is_empty() {
"::some_executor::entry_point::LastResort".to_string()
} else {
attribute.to_string()
};
let function = parse_async_fn(item)?;
let expanded = format!(
"{attrs} {vis} fn {name}() {{
<{backend} as ::some_executor::ExecutorMain>::main(async move {{
let __some_executor_main_result = async move {body}.await;
::some_executor::entry_point::MainResult::report(__some_executor_main_result);
}})
}}",
attrs = function.attributes,
vis = function.visibility,
name = function.name,
backend = backend,
body = function.body,
);
expanded
.parse()
.map_err(|e| format!("could not build the expansion: {e}"))
}
struct AsyncFn {
attributes: String,
visibility: String,
name: String,
body: String,
}
fn parse_async_fn(item: TokenStream) -> Result<AsyncFn, String> {
let tokens: Vec<TokenTree> = item.into_iter().collect();
let mut index = 0;
let mut attributes = String::new();
while index < tokens.len() {
match (&tokens[index], tokens.get(index + 1)) {
(TokenTree::Punct(punct), Some(TokenTree::Group(group)))
if punct.as_char() == '#' && group.delimiter() == Delimiter::Bracket =>
{
attributes.push_str(&tokens[index].to_string());
attributes.push_str(&group.to_string());
attributes.push(' ');
index += 2;
}
_ => break,
}
}
let mut visibility = String::new();
if let Some(TokenTree::Ident(ident)) = tokens.get(index)
&& ident.to_string() == "pub"
{
visibility.push_str("pub");
index += 1;
if let Some(TokenTree::Group(group)) = tokens.get(index)
&& group.delimiter() == Delimiter::Parenthesis
{
visibility.push_str(&group.to_string());
index += 1;
}
visibility.push(' ');
}
let keyword = |index: usize| match tokens.get(index) {
Some(TokenTree::Ident(ident)) => ident.to_string(),
_ => String::new(),
};
if keyword(index) != "async" {
return Err(
"#[some_executor::main] expects an `async fn`; a synchronous one has nothing \
to run on an executor"
.to_string(),
);
}
index += 1;
if keyword(index) != "fn" {
return Err("#[some_executor::main] expects an `async fn`".to_string());
}
index += 1;
let name = match tokens.get(index) {
Some(TokenTree::Ident(ident)) => ident.to_string(),
_ => return Err("#[some_executor::main] expects a named function".to_string()),
};
index += 1;
if let Some(TokenTree::Punct(punct)) = tokens.get(index)
&& punct.as_char() == '<'
{
return Err(
"#[some_executor::main] cannot be applied to a generic function: an entry point \
has nothing to infer its parameters from"
.to_string(),
);
}
match tokens.get(index) {
Some(TokenTree::Group(group)) if group.delimiter() == Delimiter::Parenthesis => {
if !group.stream().is_empty() {
return Err(
"#[some_executor::main] cannot be applied to a function with arguments: \
nothing would supply them"
.to_string(),
);
}
}
_ => return Err("#[some_executor::main] expects a function signature".to_string()),
}
index += 1;
let body = tokens
.iter()
.skip(index)
.find_map(|token| match token {
TokenTree::Group(group) if group.delimiter() == Delimiter::Brace => {
Some(group.to_string())
}
_ => None,
})
.ok_or_else(|| "#[some_executor::main] expects a function body".to_string())?;
Ok(AsyncFn {
attributes,
visibility,
name,
body,
})
}