extern crate proc_macro;
mod characteristic;
mod ctxt;
mod server;
mod service;
mod uuid;
use characteristic::{Characteristic, CharacteristicArgs, DescriptorArgs};
use ctxt::Ctxt;
use proc_macro::TokenStream;
use server::{ServerArgs, ServerBuilder};
use service::{ServiceArgs, ServiceBuilder};
use syn::spanned::Spanned;
use syn::{parse_macro_input, Error};
#[proc_macro_attribute]
pub fn gatt_server(args: TokenStream, item: TokenStream) -> TokenStream {
let server_args = {
let mut attributes = ServerArgs::default();
let arg_parser = syn::meta::parser(|meta| attributes.parse(meta));
syn::parse_macro_input!(args with arg_parser);
attributes
};
let ctxt = Ctxt::new();
let server_properties = syn::parse_macro_input!(item as syn::ItemStruct);
let result = ServerBuilder::new(server_properties, server_args).build();
match ctxt.check() {
Ok(()) => result.into(),
Err(e) => e.into(),
}
}
#[proc_macro_attribute]
pub fn gatt_service(args: TokenStream, item: TokenStream) -> TokenStream {
let service_arguments = parse_macro_input!(args as ServiceArgs);
let mut service_props = parse_macro_input!(item as syn::ItemStruct);
let ctxt = Ctxt::new();
let mut fields: Vec<syn::Field> = {
let struct_fields = match &mut service_props.fields {
syn::Fields::Named(n) => n,
_ => {
let s = service_props.ident;
ctxt.error_spanned_by(s, "gatt_service structs must have named fields, not tuples.");
return ctxt.check().unwrap_err().into();
}
};
struct_fields.named.iter().cloned().collect()
};
let mut characteristics: Vec<Characteristic> = Vec::new();
let mut err: Option<syn::Error> = None;
fields.retain(|field| check_for_characteristic(field, &mut err, &mut characteristics));
if let Some(err) = err {
let desc = err.to_string();
ctxt.error_spanned_by(
err.into_compile_error(),
format!("Parsing characteristics was unsuccessful:\n{desc}"),
);
return ctxt.check().unwrap_err().into();
}
let result = ServiceBuilder::new(service_props, service_arguments)
.process_characteristics_and_fields(fields, characteristics)
.build();
match ctxt.check() {
Ok(()) => result.into(),
Err(e) => e.into(),
}
}
fn check_for_characteristic(
field: &syn::Field,
err: &mut Option<syn::Error>,
characteristics: &mut Vec<Characteristic>,
) -> bool {
const RETAIN: bool = true;
const REMOVE: bool = false;
let Some(attr) = field.attrs.iter().find(|attr| attr.path().is_ident("characteristic")) else {
return RETAIN; };
let mut descriptors = Vec::new();
let mut doc_string = Vec::new();
let mut cfg = None;
let mut characteristic_checked = false;
for attr in &field.attrs {
if let Some(ident) = attr.path().get_ident() {
match ident.to_string().as_str() {
"doc" => {
doc_string.push(attr.clone());
}
"cfg" => {
if cfg.is_some() {
*err = Some(Error::new(
attr.path().span(),
"only one cfg attribute should be applied per field",
));
return REMOVE; }
cfg = Some(attr.clone());
}
"descriptor" => match DescriptorArgs::parse(attr) {
Ok(args) => descriptors.push(args),
Err(e) => {
*err = Some(e);
return REMOVE; }
},
"characteristic" => {
if characteristic_checked {
*err = Some(Error::new(
attr.path().span(),
"only one characteristic tag should be applied per field",
));
return REMOVE; } else {
characteristic_checked = true;
}
}
"descriptors" => {
*err = Some(Error::new(
attr.path().span(),
"specify a descriptor like: #[descriptor(uuid = \"1234\", value = \"Hello World\", read, write, notify)]\nCan be specified multiple times.",
));
return REMOVE; }
_ => {
*err = Some(Error::new(
attr.path().span(),
"only doc (///), descriptor and characteristic tags are supported.",
));
return REMOVE; }
}
}
}
let mut args = match CharacteristicArgs::parse(attr) {
Ok(args) => args,
Err(e) => {
*err = Some(e);
return REMOVE; }
};
args.doc_string = doc_string;
args.descriptors = descriptors;
args.cfg = cfg;
characteristics.push(Characteristic::new(field, args));
REMOVE }
#[proc_macro]
pub fn uuid(args: TokenStream) -> TokenStream {
let uuid = parse_macro_input!(args as uuid::UuidArgs);
uuid.uuid.into()
}