use proc_macro::{Literal, TokenStream, TokenTree};
use proc_macro2::Span;
use proc_macro_error::{abort, proc_macro_error};
use syn::{parse::Parse, parse_macro_input, punctuated::Punctuated, Ident, Token};
#[derive(Debug)]
struct AaInput(Vec<Ident>);
impl Parse for AaInput {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let vars = Punctuated::<Ident, Token![,]>::parse_terminated(input)?;
Ok(Self(vars.into_iter().collect()))
}
}
static AA_LIST: &[u8] = b"ARNDCEQGHILKMFPSTWYV";
#[proc_macro]
#[proc_macro_error]
pub fn any(input: TokenStream) -> TokenStream {
let parsed = parse_macro_input!(input as AaInput);
if !parsed.0.is_empty() {
abort!(Span::call_site(), "No argument allowed")
}
core::iter::once(TokenTree::Literal(Literal::string(
"[ARNDCEQGHILKMFPSTWYV]",
)))
.collect()
}
#[proc_macro]
#[proc_macro_error]
pub fn any_of(input: TokenStream) -> TokenStream {
let mut buffer = vec![b'['];
let mut counter = 1_usize;
let parsed = parse_macro_input!(input as AaInput);
if parsed.0.is_empty() {
abort!(Span::call_site(), "no amino acid to add")
}
for x in &parsed.0 {
if buffer.len() > 20 {
abort!(x.span(), "Cannot add more amino acids")
}
let as_string = x.to_string().to_uppercase();
if as_string.len() > 1 {
abort!(x.span(), "Expected only one character"; help = "only 1-letter amino acid code are accepted");
}
let aa_to_add = as_string.as_bytes().first().unwrap();
if AA_LIST.contains(aa_to_add) {
if None == buffer.iter().position(|aa| aa == aa_to_add) {
buffer.push(*aa_to_add);
counter += 1;
} else {
abort!(x.span(), "Amino acid already added");
}
} else {
abort!(x.span(), "Not an amino acid");
}
}
if counter > 2 {
buffer.push(b']');
counter += 1;
} else {
buffer.remove(0_usize);
counter -= 1;
}
core::iter::once(TokenTree::Literal(Literal::string(
core::str::from_utf8(&buffer[..counter]).unwrap(),
)))
.collect()
}
#[proc_macro]
#[proc_macro_error]
pub fn except(input: TokenStream) -> TokenStream {
let mut buffer = vec![
b'[', b'A', b'R', b'N', b'D', b'C', b'E', b'Q', b'G', b'H', b'I', b'L', b'K', b'M', b'F',
b'P', b'S', b'T', b'W', b'Y', b'V', b']',
];
let mut counter = 22_usize;
let parsed = parse_macro_input!(input as AaInput);
if parsed.0.is_empty() {
abort!(Span::call_site(), "no exceptions to add"; help = "add exceptions like `except!(P)` to avoid prolines for example")
}
for x in &parsed.0 {
if buffer.len() < 4 {
abort!(x.span(), "Cannot add more exceptions")
}
let as_string = x.to_string().to_uppercase();
if as_string.len() > 1 {
abort!(x.span(), "Expected only one character"; help = "only 1-letter amino acid code are accepted");
}
let aa_to_remove = as_string.as_bytes().first().unwrap();
if AA_LIST.contains(aa_to_remove) {
if let Some(position) = buffer.iter().position(|aa| aa == aa_to_remove) {
buffer.remove(position);
counter -= 1;
} else {
abort!(x.span(), "Exception already in place");
}
} else {
abort!(x.span(), "Not an amino acid");
}
}
core::iter::once(TokenTree::Literal(Literal::string(
core::str::from_utf8(&buffer[..counter]).unwrap(),
)))
.collect()
}