#![deny(missing_docs)]
use proc_macro::TokenStream;
use quote::quote;
use syn::{
parse::{Error as ParseError, Parse, ParseStream, Result},
parse_macro_input,
punctuated::Punctuated,
spanned::Spanned,
token::Comma,
FnArg, Ident, ItemFn, ItemUse, Pat, ReturnType, Type,
};
struct IdentList {
boards: Punctuated<Ident, Comma>,
}
impl Parse for IdentList {
fn parse(input: ParseStream) -> Result<Self> {
Ok(IdentList {
boards: input.parse_terminated(Ident::parse)?,
})
}
}
#[proc_macro_attribute]
pub fn prelude_fn(args: TokenStream, input: TokenStream) -> TokenStream {
let input_use = parse_macro_input!(input as ItemUse);
let boards = parse_macro_input!(args as IdentList);
let cfgs = boards
.boards
.iter()
.map(|board| {
let board_name = format!("{}", board);
quote!(board = #board_name)
})
.collect::<Vec<_>>();
quote!(
#[cfg(any(#(#cfgs),*, doc))]
#[cfg_attr(feature = "doc-cfg", doc(cfg(any(#(#cfgs),*))))]
#input_use
)
.into()
}
#[proc_macro_attribute]
pub fn board_fn(args: TokenStream, input: TokenStream) -> TokenStream {
let input_fn = parse_macro_input!(input as ItemFn);
let attrs = &input_fn.attrs;
let vis = &input_fn.vis;
let sig = &input_fn.sig;
let fn_name = &sig.ident;
let boards = parse_macro_input!(args as IdentList);
let module = &boards.boards[0];
let cfgs = boards
.boards
.iter()
.skip(1)
.map(|board| {
let board_name = format!("{}", board);
quote!(board = #board_name)
})
.collect::<Vec<_>>();
let args = sig
.inputs
.iter()
.filter_map(|input| match input {
FnArg::Receiver(_) => None,
FnArg::Typed(pat) => match *pat.pat {
Pat::Ident(ref ident) => Some(ident.clone()),
_ => None,
},
})
.collect::<Vec<_>>();
let impls = boards
.boards
.iter()
.skip(1)
.map(|board| {
let board_name = format!("{}", board);
quote!(
#[cfg(board = #board_name)]
{
crate::hw::board::#board::#module::#fn_name(#(#args),*)
}
)
})
.collect::<Vec<_>>();
quote!(
#[cfg(any(#(#cfgs),*, doc))]
#[cfg_attr(feature = "doc-cfg", doc(cfg(any(#(#cfgs),*))))]
#(#attrs)*
#vis #sig {
#(#impls)*
}
)
.into()
}
#[proc_macro_attribute]
pub fn entry(_args: TokenStream, input: TokenStream) -> TokenStream {
let input_fn = parse_macro_input!(input as ItemFn);
let sig = &input_fn.sig;
let fn_name = &sig.ident;
let main_is_valid = sig.asyncness.is_some()
&& sig.generics.params.is_empty()
&& sig.generics.where_clause.is_none()
&& sig.inputs.is_empty()
&& match sig.output {
ReturnType::Type(_, ref typ) => matches!(**typ, Type::Never(_)),
ReturnType::Default => false,
};
if !main_is_valid {
return ParseError::new(
input_fn.sig.span(),
format!(
"Cntrlr entry function must be of the form `async fn {}() -> !`",
fn_name
),
)
.to_compile_error()
.into();
}
quote!(
#[export_name = "__cntrlr_main"]
pub unsafe extern "C" fn #fn_name() -> ! {
#input_fn
let mut executor = ::cntrlr::task::Executor::new();
executor.add_task(#fn_name());
executor.run()
}
)
.into()
}
#[proc_macro_attribute]
pub fn raw_entry(_args: TokenStream, input: TokenStream) -> TokenStream {
let input_fn = parse_macro_input!(input as ItemFn);
let sig = &input_fn.sig;
let fn_name = &sig.ident;
let main_is_valid = sig.asyncness.is_none()
&& match sig.abi {
None => false,
Some(ref abi) => match abi.name {
None => true,
Some(ref abi) => abi.value() == "C",
},
}
&& sig.generics.params.is_empty()
&& sig.generics.where_clause.is_none()
&& sig.inputs.is_empty()
&& match sig.output {
ReturnType::Type(_, ref typ) => matches!(**typ, Type::Never(_)),
ReturnType::Default => false,
};
if !main_is_valid {
return ParseError::new(
input_fn.sig.span(),
format!(
"Cntrlr entry function must be of the form `extern \"C\" fn {}() -> !`",
fn_name
),
)
.to_compile_error()
.into();
}
quote!(
#[export_name = "__cntrlr_main"]
#input_fn
)
.into()
}
#[proc_macro_attribute]
pub fn reset(_args: TokenStream, input: TokenStream) -> TokenStream {
let input_fn = parse_macro_input!(input as ItemFn);
let sig = &input_fn.sig;
let fn_name = &sig.ident;
let reset_is_valid = sig.asyncness.is_none()
&& match sig.abi {
None => false,
Some(ref abi) => match abi.name {
None => true,
Some(ref abi) => abi.value() == "C",
},
}
&& sig.generics.params.is_empty()
&& sig.generics.where_clause.is_none()
&& sig.inputs.is_empty()
&& match sig.output {
ReturnType::Type(_, ref typ) => matches!(**typ, Type::Never(_)),
ReturnType::Default => false,
};
if !reset_is_valid {
return ParseError::new(
input_fn.sig.span(),
format!(
"Cntrlr reset function must be of the form `extern \"C\" fn {}() -> !`",
fn_name
),
)
.to_compile_error()
.into();
}
quote!(
#[link_section = ".__CNTRLR_START"]
#[export_name = "__cntrlr_reset"]
#input_fn
)
.into()
}