litutil 0.1.0

proc macros for working with literals in declarative macros
Documentation
//! this library provides several proc macros that can be called from
//! declarative macros in order to manipulate literals.

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()
	}
}



/// convert any type of string literal into a bytestring literal.
///
/// generates a compile error if its input is not a string literal.
///
/// ```
/// use litutil::into_bytestr;
/// assert_eq!(into_bytestr!("abc"), b"abc");
/// assert_eq!(into_bytestr!(c"abc"), b"abc");
/// assert_eq!(into_bytestr!(b"abc"), b"abc");
/// assert_eq!(into_bytestr!(r#"abc"#), b"abc");
/// ```
///
/// ```compile_fail
/// # use litutil::into_bytestr;
/// // this generates a compile error
/// // error: expected some kind of string literal, got 7
/// let _x = into_bytestr!(7);
/// let _y = into_bytestr!('a');1
/// ```
#[proc_macro]
pub fn into_bytestr(input: TokenStream) -> TokenStream {
	str_conv(input, |bytes, span|
			 LitByteStr::new(bytes, span).into_token_stream().into())
}

/// convert any type of string literal into a c string literal.
///
/// generates a compile error if its input is not a string literal, or
/// contains nul bytes.
///
/// ```
/// use litutil::into_cstr;
/// assert_eq!(into_cstr!("abc"), c"abc");
/// assert_eq!(into_cstr!(c"abc"), c"abc");
/// assert_eq!(into_cstr!(b"abc"), c"abc");
/// assert_eq!(into_cstr!(r#"abc"#), c"abc");
/// ```
///
/// ```compile_fail
/// # use litutil::into_cstr;
/// into_cstr!(b"\0"); // error: nul byte found in provided data at position: 0
/// ```
#[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(),
			 })
}

/// convert any type of string literal into a plain string literal.
///
/// generates a compile error if its input is not a string literal, or
/// contains invalid utf8.
///
/// ```
/// use litutil::into_str;
/// assert_eq!(into_str!("abc"), "abc");
/// assert_eq!(into_str!(c"abc"), "abc");
/// assert_eq!(into_str!(b"abc"), "abc");
/// assert_eq!(into_str!(r#"abc"#), "abc");
/// ```
///
/// ```compile_fail
/// # use litutil::into_str;
/// into_str!(b"\xFF"); // error: invalid utf-8 sequence of 1 bytes from index 0
/// ```
#[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(),
			 })
}