use core::fmt::Display;
use gprimitives::ActorId;
use proc_macro::TokenStream;
use proc_macro2::Ident;
use quote::{ToTokens, quote};
use std::{collections::BTreeSet, str::FromStr};
use syn::{
Path, Token,
parse::{Parse, ParseStream},
punctuated::Punctuated,
};
mod utils;
static mut HANDLE_REPLY_FLAG: Flag = Flag(false);
#[cfg(not(feature = "ethexe"))]
static mut HANDLE_SIGNAL_FLAG: Flag = Flag(false);
#[cfg(feature = "ethexe")]
static mut HANDLE_SIGNAL_FLAG: Flag = Flag(true);
fn literal_to_actor_id(literal: syn::LitStr) -> syn::Result<TokenStream> {
let actor_id: [u8; 32] = ActorId::from_str(&literal.value())
.map_err(|err| syn::Error::new_spanned(literal, err))?
.into();
let actor_id_array = format!("{actor_id:?}")
.parse::<proc_macro2::TokenStream>()
.expect("failed to parse token stream");
Ok(quote! { gstd::ActorId::new(#actor_id_array) }.into())
}
#[proc_macro]
pub fn actor_id(input: TokenStream) -> TokenStream {
literal_to_actor_id(syn::parse_macro_input!(input as syn::LitStr))
.unwrap_or_else(|err| err.to_compile_error().into())
}
struct Flag(bool);
impl Flag {
fn get_and_set(&mut self) -> bool {
let ret = self.0;
self.0 = true;
ret
}
}
struct MainAttrs {
handle_reply: Option<Path>,
handle_signal: Option<Path>,
}
impl MainAttrs {
fn check_attrs_not_exist(&self) -> Result<(), TokenStream> {
let Self {
handle_reply,
handle_signal,
} = self;
for (path, flag) in unsafe {
[
(handle_reply, HANDLE_REPLY_FLAG.0),
(handle_signal, HANDLE_SIGNAL_FLAG.0),
]
} {
if let (Some(path), true) = (path, flag) {
return Err(compile_error(path, "parameter already defined"));
}
}
Ok(())
}
}
impl Parse for MainAttrs {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
let punctuated: Punctuated<MainAttr, Token![,]> = Punctuated::parse_terminated(input)?;
let mut attrs = MainAttrs {
handle_reply: None,
handle_signal: None,
};
let mut existing_attrs = BTreeSet::new();
for MainAttr { name, path, .. } in punctuated {
let name = name.to_string();
if existing_attrs.contains(&name) {
return Err(syn::Error::new_spanned(name, "parameter already defined"));
}
match &*name {
"handle_reply" => {
attrs.handle_reply = Some(path);
}
#[cfg(not(feature = "ethexe"))]
"handle_signal" => {
attrs.handle_signal = Some(path);
}
#[cfg(feature = "ethexe")]
"handle_signal" => {
return Err(syn::Error::new_spanned(
name,
"`handle_signal` is forbidden with `ethexe` feature on",
));
}
_ => return Err(syn::Error::new_spanned(name, "unknown parameter")),
}
existing_attrs.insert(name);
}
Ok(attrs)
}
}
struct MainAttr {
name: Ident,
path: Path,
}
impl Parse for MainAttr {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
let name: Ident = input.parse()?;
let _: Token![=] = input.parse()?;
let path: Path = input.parse()?;
Ok(Self { name, path })
}
}
fn compile_error<T: ToTokens, U: Display>(tokens: T, msg: U) -> TokenStream {
syn::Error::new_spanned(tokens, msg)
.to_compile_error()
.into()
}
fn check_signature(name: &str, function: &syn::ItemFn) -> Result<(), TokenStream> {
if function.sig.ident != name {
return Err(compile_error(
&function.sig.ident,
format!("function must be called `{name}`"),
));
}
if !function.sig.inputs.is_empty() {
return Err(compile_error(
&function.sig.ident,
"function must have no arguments",
));
}
if function.sig.asyncness.is_none() {
return Err(compile_error(
function.sig.fn_token,
"function must be async",
));
}
Ok(())
}
fn generate_handle_reply_if_required(mut code: TokenStream, attr: Option<Path>) -> TokenStream {
#[allow(clippy::deref_addrof)] let reply_generated = unsafe { (*&raw mut HANDLE_REPLY_FLAG).get_and_set() };
if !reply_generated {
let handle_reply: TokenStream = quote!(
#[unsafe(no_mangle)]
extern "C" fn handle_reply() {
gstd::handle_reply_with_hook();
#attr ();
}
)
.into();
code.extend([handle_reply]);
}
code
}
fn generate_handle_signal_if_required(mut code: TokenStream, attr: Option<Path>) -> TokenStream {
#[allow(clippy::deref_addrof)] let signal_generated = unsafe { (*&raw mut HANDLE_SIGNAL_FLAG).get_and_set() };
if !signal_generated {
let handle_signal: TokenStream = quote!(
#[unsafe(no_mangle)]
extern "C" fn handle_signal() {
gstd::handle_signal();
#attr ();
}
)
.into();
code.extend([handle_signal]);
}
code
}
fn generate_if_required(code: TokenStream, attrs: MainAttrs) -> TokenStream {
let code = generate_handle_reply_if_required(code, attrs.handle_reply);
generate_handle_signal_if_required(code, attrs.handle_signal)
}
#[proc_macro_attribute]
pub fn async_main(attr: TokenStream, item: TokenStream) -> TokenStream {
let function = syn::parse_macro_input!(item as syn::ItemFn);
if let Err(tokens) = check_signature("main", &function) {
return tokens;
}
let attrs = syn::parse_macro_input!(attr as MainAttrs);
if let Err(tokens) = attrs.check_attrs_not_exist() {
return tokens;
}
let body = &function.block;
let code: TokenStream = quote!(
fn __main_safe() {
gstd::message_loop(async #body);
}
#[unsafe(no_mangle)]
extern "C" fn handle() {
__main_safe();
}
)
.into();
generate_if_required(code, attrs)
}
#[proc_macro_attribute]
pub fn async_init(attr: TokenStream, item: TokenStream) -> TokenStream {
let function = syn::parse_macro_input!(item as syn::ItemFn);
if let Err(tokens) = check_signature("init", &function) {
return tokens;
}
let attrs = syn::parse_macro_input!(attr as MainAttrs);
if let Err(tokens) = attrs.check_attrs_not_exist() {
return tokens;
}
let body = &function.block;
let code: TokenStream = quote!(
#[unsafe(no_mangle)]
extern "C" fn init() {
gstd::message_loop(async #body);
}
)
.into();
generate_if_required(code, attrs)
}
#[proc_macro_attribute]
pub fn wait_for_reply(attr: TokenStream, item: TokenStream) -> TokenStream {
let function = syn::parse_macro_input!(item as syn::ItemFn);
let ident = &function.sig.ident;
let (for_reply, for_reply_as) = (
utils::with_suffix(ident, "_for_reply"),
utils::with_suffix(ident, "_for_reply_as"),
);
let style = if !attr.is_empty() {
utils::DocumentationStyle::Method
} else {
utils::DocumentationStyle::Function
};
let (for_reply_docs, for_reply_as_docs) = utils::wait_for_reply_docs(ident.to_string(), style);
#[cfg_attr(feature = "ethexe", allow(unused_mut))]
let (mut inputs, variadic) = (function.sig.inputs.clone(), function.sig.variadic.clone());
let args = utils::get_args(&inputs);
#[cfg(not(feature = "ethexe"))]
inputs.push(syn::parse_quote!(reply_deposit: u64));
let decodable_ty = utils::ident("D");
let decodable_traits = vec![syn::parse_quote!(crate::codec::Decode)];
let (for_reply_generics, for_reply_as_generics) = (
function.sig.generics.clone(),
utils::append_generic(
function.sig.generics.clone(),
decodable_ty,
decodable_traits,
),
);
let ident = if !attr.is_empty() {
assert_eq!(
attr.to_string(),
"self",
"Proc macro attribute should be used only to specify self source of the function"
);
quote! { self.#ident }
} else {
quote! { #ident }
};
match () {
#[cfg(not(feature = "ethexe"))]
() => quote! {
#function
#[doc = #for_reply_docs]
pub fn #for_reply #for_reply_generics ( #inputs #variadic ) -> Result<crate::msg::MessageFuture> {
let waiting_reply_to = #ident #args ?;
if reply_deposit != 0 {
crate::exec::reply_deposit(waiting_reply_to, reply_deposit)?;
}
crate::async_runtime::signals().register_signal(waiting_reply_to);
Ok(crate::msg::MessageFuture { waiting_reply_to, reply_deposit })
}
#[doc = #for_reply_as_docs]
pub fn #for_reply_as #for_reply_as_generics ( #inputs #variadic ) -> Result<crate::msg::CodecMessageFuture<D>> {
let waiting_reply_to = #ident #args ?;
if reply_deposit != 0 {
crate::exec::reply_deposit(waiting_reply_to, reply_deposit)?;
}
crate::async_runtime::signals().register_signal(waiting_reply_to);
Ok(crate::msg::CodecMessageFuture::<D> { waiting_reply_to, reply_deposit, _marker: Default::default() })
}
},
#[cfg(feature = "ethexe")]
() => quote! {
#function
#[doc = #for_reply_docs]
pub fn #for_reply #for_reply_generics ( #inputs #variadic ) -> Result<crate::msg::MessageFuture> {
let waiting_reply_to = #ident #args ?;
crate::async_runtime::signals().register_signal(waiting_reply_to);
Ok(crate::msg::MessageFuture { waiting_reply_to, reply_deposit: 0 })
}
#[doc = #for_reply_as_docs]
pub fn #for_reply_as #for_reply_as_generics ( #inputs #variadic ) -> Result<crate::msg::CodecMessageFuture<D>> {
let waiting_reply_to = #ident #args ?;
crate::async_runtime::signals().register_signal(waiting_reply_to);
Ok(crate::msg::CodecMessageFuture::<D> { waiting_reply_to, reply_deposit: 0, _marker: Default::default() })
}
},
}.into()
}
#[proc_macro_attribute]
pub fn wait_create_program_for_reply(attr: TokenStream, item: TokenStream) -> TokenStream {
let function = syn::parse_macro_input!(item as syn::ItemFn);
let function_ident = &function.sig.ident;
let ident = if !attr.is_empty() {
assert_eq!(
attr.to_string(),
"Self",
"Proc macro attribute should be used only to specify Self source of the function"
);
quote! { Self::#function_ident }
} else {
quote! { #function_ident }
};
let (for_reply, for_reply_as) = (
utils::with_suffix(&function.sig.ident, "_for_reply"),
utils::with_suffix(&function.sig.ident, "_for_reply_as"),
);
let style = if !attr.is_empty() {
utils::DocumentationStyle::Method
} else {
utils::DocumentationStyle::Function
};
let (for_reply_docs, for_reply_as_docs) =
utils::wait_for_reply_docs(function_ident.to_string(), style);
#[cfg_attr(feature = "ethexe", allow(unused_mut))]
let (mut inputs, variadic) = (function.sig.inputs.clone(), function.sig.variadic.clone());
let args = utils::get_args(&inputs);
#[cfg(not(feature = "ethexe"))]
inputs.push(syn::parse_quote!(reply_deposit: u64));
let decodable_ty = utils::ident("D");
let decodable_traits = vec![syn::parse_quote!(crate::codec::Decode)];
let (for_reply_generics, for_reply_as_generics) = (
function.sig.generics.clone(),
utils::append_generic(
function.sig.generics.clone(),
decodable_ty,
decodable_traits,
),
);
match () {
#[cfg(not(feature = "ethexe"))]
() => quote! {
#function
#[doc = #for_reply_docs]
pub fn #for_reply #for_reply_generics ( #inputs #variadic ) -> Result<crate::msg::CreateProgramFuture> {
let (waiting_reply_to, program_id) = #ident #args ?;
if reply_deposit != 0 {
crate::exec::reply_deposit(waiting_reply_to, reply_deposit)?;
}
crate::async_runtime::signals().register_signal(waiting_reply_to);
Ok(crate::msg::CreateProgramFuture { waiting_reply_to, program_id, reply_deposit })
}
#[doc = #for_reply_as_docs]
pub fn #for_reply_as #for_reply_as_generics ( #inputs #variadic ) -> Result<crate::msg::CodecCreateProgramFuture<D>> {
let (waiting_reply_to, program_id) = #ident #args ?;
if reply_deposit != 0 {
crate::exec::reply_deposit(waiting_reply_to, reply_deposit)?;
}
crate::async_runtime::signals().register_signal(waiting_reply_to);
Ok(crate::msg::CodecCreateProgramFuture::<D> { waiting_reply_to, program_id, reply_deposit, _marker: Default::default() })
}
},
#[cfg(feature = "ethexe")]
() => quote! {
#function
#[doc = #for_reply_docs]
pub fn #for_reply #for_reply_generics ( #inputs #variadic ) -> Result<crate::msg::CreateProgramFuture> {
let (waiting_reply_to, program_id) = #ident #args ?;
crate::async_runtime::signals().register_signal(waiting_reply_to);
Ok(crate::msg::CreateProgramFuture { waiting_reply_to, program_id, reply_deposit: 0 })
}
#[doc = #for_reply_as_docs]
pub fn #for_reply_as #for_reply_as_generics ( #inputs #variadic ) -> Result<crate::msg::CodecCreateProgramFuture<D>> {
let (waiting_reply_to, program_id) = #ident #args ?;
crate::async_runtime::signals().register_signal(waiting_reply_to);
Ok(crate::msg::CodecCreateProgramFuture::<D> { waiting_reply_to, program_id, reply_deposit: 0, _marker: Default::default() })
}
},
}.into()
}
#[cfg(test)]
mod tests {
#[test]
fn ui() {
let t = trybuild::TestCases::new();
#[cfg(not(feature = "ethexe"))]
{
t.pass("tests/ui/async_init_works.rs");
t.pass("tests/ui/async_main_works.rs");
t.compile_fail("tests/ui/signal_double_definition_not_work.rs");
t.compile_fail("tests/ui/reply_double_definition_not_work.rs");
}
#[cfg(feature = "ethexe")]
{
t.compile_fail("tests/ui/signal_doesnt_work_with_ethexe.rs");
}
}
}