use std::ffi::CString;
use proc_macro::TokenStream;
use syn::{Lit, LitStr, LitCStr, LitByteStr, parse_macro_input};
use quote::{ToTokens, quote};
use proc_macro2::Span;
trait Literal {
type Value;
fn get_value(&self) -> Self::Value;
}
macro_rules! impl_lit {
($lit_t:path, $value_t:path) => {
impl $crate::Literal for $lit_t {
type Value = $value_t;
fn get_value(&self) -> $value_t {
self.value()
}
}
};
}
impl_lit!(LitStr, String);
impl_lit!(LitCStr, CString);
impl_lit!(LitByteStr, Vec<u8>);
fn str_lit_bytes(lit: &Lit) -> Option<Vec<u8>> {
match lit {
Lit::Str(l) => Some(l.get_value().as_bytes().to_owned()),
Lit::CStr(l) => Some(l.get_value().to_bytes().to_owned()),
Lit::ByteStr(l) => Some(l.get_value()),
_ => None,
}
}
fn str_conv(input: TokenStream, f: impl Fn(&[u8], Span) -> TokenStream)
-> TokenStream
{
let lit = parse_macro_input!(input as Lit);
if let Some(bytes) = str_lit_bytes(&lit) {
f(&bytes, lit.span())
} else {
quote!{
compile_error!(concat!("expected some kind of string literal, got ", stringify!(#lit)));
}.into()
}
}
#[proc_macro]
pub fn into_bytestr(input: TokenStream) -> TokenStream {
str_conv(input, |bytes, span|
LitByteStr::new(bytes, span).into_token_stream().into())
}
#[proc_macro]
pub fn into_cstr(input: TokenStream) -> TokenStream {
str_conv(input, |bytes, span|
match CString::new(bytes) {
Ok(cs) => LitCStr::new(&cs, span).into_token_stream().into(),
Err(e) => syn::Error::new(span, e).into_compile_error().into(),
})
}
#[proc_macro]
pub fn into_str(input: TokenStream) -> TokenStream {
str_conv(input, |bytes, span|
match std::str::from_utf8(bytes) {
Ok(s) => LitStr::new(&s, span).into_token_stream().into(),
Err(e) => syn::Error::new(span, e).into_compile_error().into(),
})
}