byte_strings_proc_macro/
c_str.rs1::cfg_if::cfg_if![ if #[cfg(feature = "proc-macro-hygiene")]
2{
3 #[cfg(any())]
4 mod objective {
5 macro_rules! c_str {(
6 $( $input_str_literal:expr ),+ $(,)?
7 ) => (
8 unsafe {
9 ::std::ffi::CStr::from_bytes_with_nul_unchecked(
10 $crate::concat_bytes!(
11 $(
12 $string_literal ,
13 )+
14 b"\0",
15 )
16 )
17 }
18 )}
19 }
20
21 #[doc(hidden)]
22 #[proc_macro]
23 pub fn c_str (
24 input: proc_macro::TokenStream,
25 ) -> proc_macro::TokenStream
26 {
27 use ::syn::parse::Parser;
28
29 let input_exprs = match CommaExprs::parse_terminated.parse(input) {
30 Ok(input_exprs) => input_exprs,
31
32 Err(err) => throw!(err.span()=>
33 "Could not parse a comma-separated sequence of expressions"
34 ),
35 };
36
37 let mut input_exprs = input_exprs.into_iter().peekable();
38 let expr_span = match input_exprs.peek() {
39 Some(expr) => expr.span().clone(),
40 _ => throw!(
41 "expected at least one argument"
42 ),
43 };
44
45 let mut bytes: Vec<u8> = Vec::new();
46
47 for expr in input_exprs {
48 match expr {
49 syn::Expr::Lit(
50 syn::ExprLit {
51 lit: syn::Lit::ByteStr(ref bytestr_literal),
52 ..
53 }
54 ) => {
55 match &bytestr_literal.value()[..] { literal_bytes => {
56 if literal_bytes.contains(&0) {
57 throw!(expr.span()=>
58 "input literals cannot contain null bytes"
59 );
60 }
61 bytes.extend_from_slice(literal_bytes);
62 }}
63 },
64 syn::Expr::Lit(
65 syn::ExprLit {
66 lit: syn::Lit::Str(ref str_literal),
67 ..
68 }
69 ) => {
70 match str_literal.value().as_bytes() { literal_bytes => {
71 if literal_bytes.contains(&0) {
72 throw!(expr.span()=>
73 "input literals cannot contain null bytes"
74 );
75 }
76 bytes.extend_from_slice(literal_bytes);
77 }}
78 },
79
80 _ => throw!(expr.span()=>
81 "expected a string literal (or a byte string literal)"
82 ),
83 }
84 };
85
86 bytes.reserve_exact(1); bytes.push(0);
87
88 let lit = syn::Lit::
89 ByteStr(syn::LitByteStr::new(
90 &bytes,
91 expr_span,
92 ))
93 ;
94 let bytes = syn::Expr::
95 Lit(syn::ExprLit { attrs: Vec::default(), lit })
96 ;
97
98 proc_macro::TokenStream::from(quote::quote!{
99 unsafe {
100 ::std::ffi::CStr::from_bytes_with_nul_unchecked(
101 #bytes
102 )
103 }
104 })
105 }
106}
107else
108{
109 #[cfg(any())]
110 mod objective {
111 macro_rules! const_c_str {(
112 const $const_literal_name:ident = c_str!(
113 $input_expr:expr
114 );
115 ) => (
116 const $const_literal_name: &[u8; _] = c_str!(input_expr);
117 )}
118 }
119
120 struct ConstCStr {
121 const_literal_name: syn::Ident,
122 input_exprs: CommaExprs,
123 }
124
125 impl Parse for ConstCStr
126 {
127 fn parse (input: syn::parse::ParseStream) -> syn::parse::Result<Self>
128 {
129 macro_rules! parse_token {($tt:tt) => (
130 input.parse::<syn::Token![$tt]>()
131 )}
132
133 parse_token!( const )?;
134 let const_literal_name: syn::Ident = input.parse()?;
135 parse_token!( = )?;
136 input.parse::<kw::c_str>()?;
137 parse_token!( ! )?;
138 let input_exprs: CommaExprs = syn::group::
139 parse_parens(&input)?
140 .content
141 .parse_terminated(syn::Expr::parse)?
142 ;
143 parse_token!( ; )?;
144 Ok(ConstCStr {
145 const_literal_name,
146 input_exprs,
147 })
148 }
149 }
150
151 #[proc_macro]
152 pub fn const_c_str (
153 input: proc_macro::TokenStream,
154 ) -> proc_macro::TokenStream
155 {
156 let ConstCStr {
157 const_literal_name,
158 input_exprs,
159 } = syn::parse_macro_input!(input);
160
161 let mut input_exprs = input_exprs.into_iter().peekable();
162 let expr_span = match input_exprs.peek() {
163 Some(expr) => expr.span().clone(),
164 _ => throw!(
165 "expected at least one argument"
166 ),
167 };
168
169 let mut bytes: Vec<u8> = Vec::new();
170
171 for expr in input_exprs {
172 match expr {
173 syn::Expr::Lit(
174 syn::ExprLit {
175 lit: syn::Lit::ByteStr(ref bytestr_literal),
176 ..
177 }
178 ) => {
179 match &bytestr_literal.value()[..] { literal_bytes => {
180 if literal_bytes.contains(&0) {
181 throw!(expr.span()=>
182 "input literals cannot contain null bytes"
183 );
184 }
185 bytes.extend_from_slice(literal_bytes);
186 }}
187 },
188 syn::Expr::Lit(
189 syn::ExprLit {
190 lit: syn::Lit::Str(ref str_literal),
191 ..
192 }
193 ) => {
194 match str_literal.value().as_bytes() { literal_bytes => {
195 if literal_bytes.contains(&0) {
196 throw!(expr.span()=>
197 "input literals cannot contain null bytes"
198 );
199 }
200 bytes.extend_from_slice(literal_bytes);
201 }}
202 },
203
204 _ => throw!(expr.span()=>
205 "expected a string literal (or a byte string literal)"
206 ),
207 }
208 };
209
210 bytes.reserve_exact(1); bytes.push(0);
211
212 let bytes = syn::Expr::
213 Lit(syn::ExprLit {
214 attrs: Vec::default(),
215
216 lit: syn::Lit::ByteStr(syn::LitByteStr::new(
217 &bytes,
219
220 expr_span,
222 ))
223 })
224 ;
225
226 proc_macro::TokenStream::from(quote::quote! {
227 union transmute {
228 src: &'static [u8],
229 dst: &'static ::std::ffi::CStr,
230 }
231
232 const transmute_is_sound_guard: [();
233 ::std::mem::size_of::<&'static [u8]>()
234 ] = [();
235 ::std::mem::size_of::<&'static ::std::ffi::CStr>()
236 ];
237
238 const #const_literal_name: &'static ::std::ffi::CStr = unsafe {
239 (transmute { src: #bytes }).dst
240 };
241 })
242 }
243}];