Skip to main content

byte_strings_proc_macro/
c_str.rs

1::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                    // value
218                    &bytes,
219
220                    // Span
221                    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}];