use proc_macro::TokenStream;
use quote::quote;
use syn::{FnArg, Item, ItemFn, Pat, PatIdent, parse_macro_input};
#[proc_macro_attribute]
pub fn rpc(_args: TokenStream, input: TokenStream) -> TokenStream {
let item = parse_macro_input!(input as Item);
quote! {
#[derive(fox::serde::Serialize, fox::serde::Deserialize)]
#[serde(crate = "fox::serde")]
#item
}
.into()
}
#[proc_macro_attribute]
pub fn main(_args: TokenStream, input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as ItemFn);
let (attrs, vis, sig, block) = (&input.attrs, &input.vis, &input.sig, &input.block);
quote! {
#(#attrs)*
#vis #sig {
#[cfg(not(any(feature = "orchestrator", feature = "remote")))]
compile_error!("None of the required features were selected");
if cfg!(feature = "orchestrator") {
#block
} else {
fox::remote_rpc()
}
}
}
.into()
}
#[proc_macro_attribute]
pub fn remote(_args: TokenStream, input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as ItemFn);
let (attrs, vis, sig, block) = (&input.attrs, &input.vis, &input.sig, &input.block);
let function_ident = &sig.ident;
let mut arg_idents = vec![];
let mut arg_types = vec![];
for input_arg in &sig.inputs {
match input_arg {
FnArg::Receiver(..) => panic!("Argument `self` not allowed"),
FnArg::Typed(pat_type) => match &*pat_type.pat {
Pat::Ident(PatIdent { ident, .. }) => {
arg_idents.push(ident);
arg_types.push(&pat_type.ty)
}
other => panic!("Only standard named arguments are supported. Found: {other:?}"),
},
}
}
let type_signature = arg_types
.iter()
.map(|ty| quote! { #ty }.to_string().replace(' ', ""))
.collect::<Vec<String>>()
.join(",");
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap_or_default();
let absolute_span_path = std::path::PathBuf::from(proc_macro::Span::call_site().file());
let relative_path = absolute_span_path
.strip_prefix(&manifest_dir)
.unwrap_or(&absolute_span_path)
.to_string_lossy()
.to_string();
let crate_name = std::env::var("CARGO_PKG_NAME").unwrap_or_default();
let bulletproof_seed = format!(
"{}::{}::{} Colonial_({})",
crate_name, relative_path, function_ident, type_signature
);
let uuid =
uuid::Uuid::new_v5(&uuid::Uuid::NAMESPACE_DNS, bulletproof_seed.as_bytes()).to_string();
quote! {
#(#attrs)*
#vis #sig {
#[cfg(not(any(feature = "orchestrator", feature = "remote")))]
compile_error!("None of the required features were selected");
if cfg!(feature = "orchestrator") {
let rpc_payload = fox::RpcPayload {
uuid: #uuid.to_string(),
data: fox::postcard::to_allocvec(&( #(#arg_idents),* ))
.expect("Failed to serialize args"),
};
let outbound_bytes =
fox::postcard::to_allocvec_cobs(&rpc_payload)
.expect("Failed to serialize payload");
use ::std::io::Write as _;
let mut stdout = ::std::io::stdout().lock();
stdout.write_all(&outbound_bytes).expect("Failed to write to stdout");
stdout.flush().expect("Failed to flush stdout");
use std::io::BufRead as _;
let mut inbound_bytes = ::std::vec::Vec::new();
::std::io::stdin().lock()
.read_until(0x00, &mut inbound_bytes)
.expect("Failed to read stdin");
fox::postcard::from_bytes_cobs(&mut inbound_bytes)
.expect("Failed to deserialize return value")
} else {
#block
}
}
#[cfg(feature = "remote")]
fox::inventory::submit! {
fox::RemoteFn {
uuid: #uuid,
function: |args_bytes| {
let ( #(#arg_idents),* ): ( #(#arg_types),* ) =
fox::postcard::from_bytes(&args_bytes)
.expect("Failed to deserialize args");
fox::postcard::to_allocvec_cobs(&#function_ident( #(#arg_idents),* ))
.expect("Failed to serialize return value")
},
}
}
}
.into()
}