agb/agb-macros/src/lib.rs

127 lines
3.8 KiB
Rust
Raw Normal View History

2021-08-08 01:24:19 +10:00
// This macro is based very heavily on the entry one in rust-embedded
use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::{quote, ToTokens};
use syn::{FnArg, Ident, ItemFn, Pat, ReturnType, Token, Type, Visibility};
2021-08-08 01:24:19 +10:00
2022-03-06 07:51:58 +11:00
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
2021-08-08 01:24:19 +10:00
#[proc_macro_attribute]
pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream {
let f: ItemFn = syn::parse(input).expect("#[agb::entry] must be applied to a function");
// Check that the function signature is correct
assert!(
f.sig.constness.is_none()
&& f.vis == Visibility::Inherited
&& f.sig.abi.is_none()
&& f.sig.generics.params.is_empty()
&& f.sig.generics.where_clause.is_none()
&& match f.sig.output {
2021-08-08 01:51:14 +10:00
ReturnType::Type(_, ref ty) => matches!(**ty, Type::Never(_)),
2021-08-08 01:24:19 +10:00
_ => false,
},
"#[agb::entry] must have signature [unsafe] fn (mut agb::Gba) -> !"
2021-08-08 01:24:19 +10:00
);
// Check that the function signature takes 1 argument, agb::Gba
let arguments: Vec<_> = f.sig.inputs.iter().collect();
assert_eq!(
arguments.len(),
1,
"#[agb::entry] must have signature [unsafe] fn (mut agb::Gba) -> !, but got {} arguments",
arguments.len(),
);
let (argument_type, (argument_name, is_mutable)) = match arguments[0] {
FnArg::Typed(pat_type) => (
pat_type.ty.to_token_stream(),
match &*pat_type.pat {
Pat::Ident(ident) => {
assert!(
ident.attrs.is_empty() && ident.by_ref.is_none() && ident.subpat.is_none(),
"#[agb::entry] must have signature [unsafe] fn (mut agb::Gba) -> !"
);
(ident.ident.clone(), ident.mutability.is_some())
}
_ => panic!("Expected first argument to #[agb::entry] to be a basic identifier"),
},
),
_ => panic!("Expected first argument to #[agb::entry] to not be self"),
};
2021-08-08 01:53:13 +10:00
assert!(
args.to_string() == "",
"Must pass no args to #[agb::entry] macro"
);
2021-08-08 01:24:19 +10:00
2022-03-06 07:51:58 +11:00
let fn_name = hashed_ident(&f);
2021-08-08 01:24:19 +10:00
let attrs = f.attrs;
let stmts = f.block.stmts;
let mutable = if is_mutable {
Some(Token![mut](Span::call_site()))
} else {
None
};
assert!(
argument_type.to_string().ends_with("Gba"),
"Expected first argument to have type 'Gba'"
);
2021-08-08 01:24:19 +10:00
quote!(
#[cfg(not(test))]
2021-08-08 01:24:19 +10:00
#[export_name = "main"]
#(#attrs)*
pub fn #fn_name() -> ! {
2022-01-17 08:50:18 +11:00
let #mutable #argument_name = unsafe { #argument_type ::new_in_entry() };
2021-08-08 01:24:19 +10:00
#(#stmts)*
}
#[cfg(test)]
#[export_name = "main"]
#(#attrs)*
pub fn #fn_name() -> ! {
let mut #argument_name = unsafe { #argument_type ::new_in_entry() };
if cfg!(test) {
::agb::test_runner::agb_start_tests(#argument_name, test_main);
} else {
#(#stmts)*
}
}
2021-08-08 01:53:13 +10:00
)
.into()
2021-08-08 01:24:19 +10:00
}
#[proc_macro]
pub fn num(input: TokenStream) -> TokenStream {
let f = syn::parse_macro_input!(input as syn::LitFloat);
let v: f64 = f.base10_parse().expect("The number should be parsable");
let integer = v.trunc();
let fractional = v.fract() * (1_u64 << 30) as f64;
let integer = integer as i32;
let fractional = fractional as i32;
quote!((#integer, #fractional)).into()
}
2022-03-06 07:51:58 +11:00
fn hashed_ident<T: Hash>(f: &T) -> Ident {
let hash = calculate_hash(f);
Ident::new(&format!("_agb_main_func_{}", hash), Span::call_site())
}
fn calculate_hash<T: Hash>(t: &T) -> u64 {
let mut s = DefaultHasher::new();
t.hash(&mut s);
s.finish()
2021-08-08 01:24:19 +10:00
}