litutil/lib.rs
1//! this library provides several proc macros that can be called from
2//! declarative macros in order to manipulate literals.
3
4use std::ffi::CString;
5use proc_macro::TokenStream;
6use syn::{Lit, LitStr, LitCStr, LitByteStr, parse_macro_input};
7use quote::{ToTokens, quote};
8use proc_macro2::Span;
9
10trait Literal {
11 type Value;
12 fn get_value(&self) -> Self::Value;
13}
14
15macro_rules! impl_lit {
16 ($lit_t:path, $value_t:path) => {
17 impl $crate::Literal for $lit_t {
18 type Value = $value_t;
19 fn get_value(&self) -> $value_t {
20 self.value()
21 }
22 }
23 };
24}
25
26impl_lit!(LitStr, String);
27impl_lit!(LitCStr, CString);
28impl_lit!(LitByteStr, Vec<u8>);
29
30fn str_lit_bytes(lit: &Lit) -> Option<Vec<u8>> {
31 match lit {
32 Lit::Str(l) => Some(l.get_value().as_bytes().to_owned()),
33 Lit::CStr(l) => Some(l.get_value().to_bytes().to_owned()),
34 Lit::ByteStr(l) => Some(l.get_value()),
35 _ => None,
36 }
37}
38
39fn str_conv(input: TokenStream, f: impl Fn(&[u8], Span) -> TokenStream)
40 -> TokenStream
41{
42 let lit = parse_macro_input!(input as Lit);
43 if let Some(bytes) = str_lit_bytes(&lit) {
44 f(&bytes, lit.span())
45 } else {
46 quote!{
47 compile_error!(concat!("expected some kind of string literal, got ", stringify!(#lit)));
48 }.into()
49 }
50}
51
52
53
54/// convert any type of string literal into a bytestring literal.
55///
56/// generates a compile error if its input is not a string literal.
57///
58/// ```
59/// use litutil::into_bytestr;
60/// assert_eq!(into_bytestr!("abc"), b"abc");
61/// assert_eq!(into_bytestr!(c"abc"), b"abc");
62/// assert_eq!(into_bytestr!(b"abc"), b"abc");
63/// assert_eq!(into_bytestr!(r#"abc"#), b"abc");
64/// ```
65///
66/// ```compile_fail
67/// # use litutil::into_bytestr;
68/// // this generates a compile error
69/// // error: expected some kind of string literal, got 7
70/// let _x = into_bytestr!(7);
71/// let _y = into_bytestr!('a');1
72/// ```
73#[proc_macro]
74pub fn into_bytestr(input: TokenStream) -> TokenStream {
75 str_conv(input, |bytes, span|
76 LitByteStr::new(bytes, span).into_token_stream().into())
77}
78
79/// convert any type of string literal into a c string literal.
80///
81/// generates a compile error if its input is not a string literal, or
82/// contains nul bytes.
83///
84/// ```
85/// use litutil::into_cstr;
86/// assert_eq!(into_cstr!("abc"), c"abc");
87/// assert_eq!(into_cstr!(c"abc"), c"abc");
88/// assert_eq!(into_cstr!(b"abc"), c"abc");
89/// assert_eq!(into_cstr!(r#"abc"#), c"abc");
90/// ```
91///
92/// ```compile_fail
93/// # use litutil::into_cstr;
94/// into_cstr!(b"\0"); // error: nul byte found in provided data at position: 0
95/// ```
96#[proc_macro]
97pub fn into_cstr(input: TokenStream) -> TokenStream {
98 str_conv(input, |bytes, span|
99 match CString::new(bytes) {
100 Ok(cs) => LitCStr::new(&cs, span).into_token_stream().into(),
101 Err(e) => syn::Error::new(span, e).into_compile_error().into(),
102 })
103}
104
105/// convert any type of string literal into a plain string literal.
106///
107/// generates a compile error if its input is not a string literal, or
108/// contains invalid utf8.
109///
110/// ```
111/// use litutil::into_str;
112/// assert_eq!(into_str!("abc"), "abc");
113/// assert_eq!(into_str!(c"abc"), "abc");
114/// assert_eq!(into_str!(b"abc"), "abc");
115/// assert_eq!(into_str!(r#"abc"#), "abc");
116/// ```
117///
118/// ```compile_fail
119/// # use litutil::into_str;
120/// into_str!(b"\xFF"); // error: invalid utf-8 sequence of 1 bytes from index 0
121/// ```
122#[proc_macro]
123pub fn into_str(input: TokenStream) -> TokenStream {
124 str_conv(input, |bytes, span|
125 match std::str::from_utf8(bytes) {
126 Ok(s) => LitStr::new(&s, span).into_token_stream().into(),
127 Err(e) => syn::Error::new(span, e).into_compile_error().into(),
128 })
129}