Improve #[entry] macro

This commit is contained in:
BlueTheDuck 2024-08-06 15:19:56 -03:00
parent dd5ae49909
commit 4b58ae07ed
No known key found for this signature in database
GPG Key ID: 256464D859A672F3
3 changed files with 115 additions and 35 deletions

View File

@ -11,4 +11,4 @@ proc-macro = true
[dependencies]
syn = { version = "2.0", features = ["full"]}
quote = "1.0"
quote = "1.0"

View File

@ -1,21 +1,109 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, ItemFn, Meta, MetaNameValue, ReturnType, Type};
use syn::{
parse_macro_input, spanned::Spanned, Attribute, FnArg, ItemFn, Meta, MetaNameValue, PatType,
ReturnType, Type, Visibility,
};
/// Use it to mark the ROM entry point.
/// Allows only `fn(Hw) -> !`
fn check_sig(signature: &syn::Signature) -> Result<(), syn::Error> {
let span = signature.span();
if let ReturnType::Type(_, ref t) = signature.output {
match **t {
Type::Never(_) => {}
Type::Infer(_) => {
return Err(syn::Error::new(
span,
"Add `-> !`. To avoid ambiguity, please specify the return type as `!`",
));
}
_ => {
return Err(syn::Error::new(
span,
"Change to `-> !`. The entry point must be diverging. E.g. `fn() -> !`",
));
}
}
} else {
return Err(syn::Error::new(
span,
"Add `-> !`. To avoid ambiguity, please specify the return type as `!`.",
));
}
if signature.inputs.len() != 1 {
return Err(syn::Error::new(
span,
"Incorrect arguments. The entry point must take exactly one argument of type `Hw`",
));
} else {
let arg = signature.inputs.first().unwrap();
if let FnArg::Typed(PatType { ty, .. }) = arg {
if let Type::Path(ref path) = **ty {
if path.path.segments.last().unwrap().ident != "Hw" {
return Err(syn::Error::new(
arg.span(),
"Change type. The entry point must take exactly one argument of type `Hw`",
));
}
} else {
return Err(syn::Error::new(
arg.span(),
"Change type. The entry point must take exactly one argument of type `Hw`",
));
}
} else {
return Err(syn::Error::new(
arg.span(),
"Change type. The entry point can not be a method.",
));
}
}
Ok(())
}
/// Allow only `pub` visibility
fn check_vis(vis: &Visibility) -> Result<(), syn::Error> {
if let Visibility::Public(..) = vis {
Ok(())
} else {
Err(syn::Error::new(
vis.span(),
"Add `pub`. The entry point must be public and without restriction.",
))
}
}
/// Forbids `#[no_mangle]` and `#[link_name = ...]`
fn check_attr(attrs: &Vec<Attribute>) -> Result<(), syn::Error> {
for attr in attrs {
match &attr.meta {
Meta::NameValue(MetaNameValue { path, .. }) if path.is_ident("export_name") => {
return Err(syn::Error::new(path.span(), "Remove this `link_name`. The entry point requires a specific symbol name that it's set by the `entry` attribute."))
}
Meta::Path(path) if path.is_ident("no_mangle") => {
return Err(syn::Error::new(path.span(), "Remove this `no_mangle`. The entry point requires a specific symbol name that it's set by the `entry` attribute."))
}
_ => {}
}
}
Ok(())
}
/// Use it to mark the entry point of your program.
///
/// Example:
/// # Example:
/// ```rust,no_run
/// #![no_main]
///
/// #[macro_use]
/// extern crate nds:
/// extern crate nds_proc_macros:
///
/// #[entry]
/// fn main(mut hw: nds::Hw) -> ! {
/// loop {
/// nds::interrupts::swi_wait_for_v_blank();
/// }
/// pub fn main(_: nds::Hw) -> ! {
/// loop { }
/// }
/// ```
#[proc_macro_attribute]
@ -24,37 +112,24 @@ pub fn entry(_: TokenStream, input: TokenStream) -> TokenStream {
// Check the signature is correct:
// - Must be `fn() -> !`
// - Must be `pub`
// - Must have 1 argument
// - Must not have `link_name` or `no_mangle` attributes
// - Must be `pub`
// - Must not have `export_name` or `no_mangle` attributes
if let ReturnType::Type(_, t) = &input.sig.output {
assert!(
matches!(**t, Type::Never(_)),
"The function must return `!`"
);
} else {
panic!("The function must return `!`");
if let Err(e) = check_sig(&input.sig) {
return e.to_compile_error().into();
}
assert!(matches!(input.vis, syn::Visibility::Public(_)));
if let Err(e) = check_vis(&input.vis) {
return e.to_compile_error().into();
}
assert_eq!(input.sig.inputs.len(), 1);
for attr in &input.attrs {
match &attr.meta {
Meta::NameValue(MetaNameValue { path, .. }) if path.is_ident("link_name") => {
panic!("The `link_name` attribute is not allowed here");
}
Meta::Path(path) if path.is_ident("no_mangle") => {
panic!("The `no_mangle` attribute is not allowed here");
}
_ => {}
}
if let Err(e) = check_attr(&input.attrs) {
return e.to_compile_error().into();
}
quote! {
#[link_name = "rust_main"]
#[export_name = "__rust_user_main"]
#input
}
.into()

View File

@ -2,14 +2,19 @@
use nds_rs::Hw;
/// Entry point called from the C runtime
///
/// # Safety
///
/// Must never be called by user code.
#[no_mangle]
pub extern "C" fn main() -> ! {
pub unsafe extern "C" fn main() -> ! {
extern "Rust" {
#[link_name = "rust_main"]
#[link_name = "__rust_user_main"]
fn main(hw: Hw) -> !;
}
let peripherals = Hw::take().unwrap();
let peripherals = Hw::take().unwrap_unchecked();
unsafe {
main(peripherals);
}