use heck::ToPascalCase;
use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::quote;
use syn::{
Ident, Token, braced,
parse::{Parse, ParseStream},
punctuated::Punctuated,
};
struct TeeServiceInput {
commands: Vec<Ident>,
context: Option<Ident>,
}
mod keywords {
syn::custom_keyword!(context);
syn::custom_keyword!(commands);
}
impl Parse for TeeServiceInput {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut commands: Option<Vec<Ident>> = None;
let mut context: Option<Ident> = None;
while !input.is_empty() {
let lookahead = input.lookahead1();
if lookahead.peek(keywords::context) {
if context.is_some() {
return Err(syn::Error::new(input.span(), "Duplicate 'context' section"));
}
input.parse::<keywords::context>()?;
let ctx_ident: Ident = input.parse()?;
context = Some(ctx_ident);
} else if lookahead.peek(keywords::commands) {
if commands.is_some() {
return Err(syn::Error::new(
input.span(),
"Duplicate 'commands' section",
));
}
input.parse::<keywords::commands>()?;
let content;
braced!(content in input);
let idents = Punctuated::<Ident, Token![,]>::parse_terminated(&content)?;
if idents
.iter()
.any(|ident| ident.to_string().eq_ignore_ascii_case("unknown"))
{
return Err(syn::Error::new(
input.span(),
"Command name 'Unknown' is reserved for the default case",
));
}
commands = Some(idents.into_iter().collect());
} else {
return Err(lookahead.error());
}
}
Ok(TeeServiceInput {
commands: commands.unwrap_or_default(),
context,
})
}
}
pub fn tee_service(input: TokenStream2) -> TokenStream2 {
let TeeServiceInput { commands, context } = match syn::parse2::<TeeServiceInput>(input) {
Ok(input) => input,
Err(err) => return err.into_compile_error(),
};
let variant_idents: Vec<Ident> = commands
.iter()
.map(|cmd| Ident::new(&cmd.to_string().to_pascal_case(), cmd.span()))
.collect();
let discriminants: Vec<_> = (0u64..)
.take(commands.len())
.map(|n| syn::LitInt::new(&n.to_string(), Span::call_site()))
.collect();
let unknown_discriminant = syn::LitInt::new(&commands.len().to_string(), Span::call_site());
let command_enum = quote! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[repr(u32)]
pub enum Command {
#(#variant_idents = #discriminants,)*
#[default]
Unknown = #unknown_discriminant,
}
};
let impl_primitive = quote! {
impl From<u32> for Command {
fn from(val: u32) -> Self {
match val {
#(#discriminants => Command::#variant_idents,)*
_ => Command::Unknown,
}
}
}
impl From<Command> for u32 {
fn from(cmd: Command) -> Self {
cmd as u32
}
}
};
let dispatch_fns: Vec<Ident> = commands
.iter()
.map(|cmd| Ident::new(&format!("{}_dispatched", cmd), cmd.span()))
.collect();
let invoke_fn = if let Some(ctx_ident) = context {
quote! {
#[cfg(feature = "ta")]
pub fn invoke_command(
ctx: &mut #ctx_ident,
cmd: u32,
params: &mut ::optee_utee::Parameters,
) -> ::optee_utee::Result<()> {
match Command::from(cmd) {
#(Command::#variant_idents => #dispatch_fns(ctx, params),)*
Command::Unknown => Err(::optee_utee::ErrorKind::BadParameters.into()),
}
}
}
} else {
quote! {
#[cfg(feature = "ta")]
pub fn invoke_command(
cmd: u32,
params: &mut ::optee_utee::Parameters,
) -> ::optee_utee::Result<()> {
match Command::from(cmd) {
#(Command::#variant_idents => #dispatch_fns(params),)*
Command::Unknown => Err(::optee_utee::ErrorKind::BadParameters.into()),
}
}
}
};
quote! {
#command_enum
#impl_primitive
#invoke_fn
}
}