#![doc = include_str!("../README.md")]
#![no_std]
#![deny(missing_docs)]
#![warn(clippy::all, clippy::nursery, clippy::cargo)]
#[cfg(feature = "log")]
use log::debug;
use proc_macro::{Delimiter, Group, Punct, Spacing, TokenStream, TokenTree};
macro_rules! error {
($msg:literal) => {{
::core::stringify! {
::core::compile_error!($msg)
}
.parse()
.unwrap()
}};
}
#[derive(Debug)]
enum MacroError {
EmptyInput,
InvalidMacroName,
}
impl MacroError {
fn into_token_stream(self) -> TokenStream {
match self {
Self::EmptyInput => {
error!("Expected an identifier, found nothing")
}
Self::InvalidMacroName => {
error!("Expected an identifier, found something else")
}
}
}
}
#[proc_macro_attribute]
pub fn apply(attrs: TokenStream, input: TokenStream) -> TokenStream {
let (macro_name, macro_append) = match split_macro_name_and_append(attrs) {
Ok(result) => result,
Err(err) => return err.into_token_stream(),
};
invoke_macro(macro_name.into(), macro_append, input)
}
#[proc_macro_attribute]
pub fn extend(attrs: TokenStream, mut input: TokenStream) -> TokenStream {
let (macro_name, macro_append) = match split_macro_name_and_append(attrs) {
Ok(result) => result,
Err(err) => return err.into_token_stream(),
};
let invoked = invoke_macro(macro_name.into(), macro_append, input.clone());
input.extend(invoked);
input
}
fn split_macro_name_and_append(input: TokenStream) -> Result<(TokenTree, TokenStream), MacroError> {
let mut tts = input.into_iter();
let macro_name = tts.next().ok_or(MacroError::EmptyInput)?;
if !matches!(macro_name, TokenTree::Ident(_)) {
return Err(MacroError::InvalidMacroName);
}
let macro_append = tts.collect();
#[cfg(feature = "log")]
debug!("macro_name: {macro_name:?}");
#[cfg(feature = "log")]
debug!("macro_append: {macro_append:?}");
Ok((macro_name, macro_append))
}
fn invoke_macro(
macro_name: TokenStream,
macro_append: TokenStream,
input: TokenStream,
) -> TokenStream {
let mut macro_input = input;
let mut ret = macro_name;
macro_input.extend(macro_append); ret.extend([TokenTree::Punct(Punct::new('!', Spacing::Alone))]); ret.extend([TokenTree::Group(Group::new(Delimiter::Brace, macro_input))]);
#[cfg(feature = "log")]
debug!("ret: {ret:?}");
ret
}