1#![allow(non_snake_case)]
16#![allow(clippy::redundant_static_lifetimes)]
17#![allow(clippy::tabs_in_doc_comments)]
18#![allow(clippy::needless_doctest_main)]
19
20#![no_std]
44
45extern crate proc_macro;
46extern crate alloc;
47
48use core::ffi::CStr;
49use alloc::borrow::Cow;
50use alloc::string::{String, ToString};
51use core::num::NonZeroU8;
52use alloc::vec::Vec;
53use quote::quote;
54use proc_macro::TokenStream;
55use proc_macro2::{TokenTree as TokenTree2, Literal, Span};
56
57#[inline]
60fn __make_pm_compile_error(span: Span, message: &str) -> TokenStream {
61 TokenStream::from(quote::quote_spanned! {
62 span =>
63 compile_error! { #message }
64 })
65}
66
67macro_rules! pm_compile_error {
70 ($span: expr, $e: expr) => {{
71 return __make_pm_compile_error($span, $e);
72 }};
73}
74
75macro_rules! thiserr_nullbyte {
78 [
79 $lit: ident, $e: expr $(,)?
80 ] => {{
81 let e: Result<(), ErrDetectedNullByte> = $e; if e.is_err() {
84 pm_compile_error!($lit.span(), "Format convention error, null byte detected.");
85 }
86 }};
87}
88
89struct ErrDetectedNullByte;
92
93struct SafeCStrBuilder(Vec<u8>);
98
99impl SafeCStrBuilder {
100 #[inline(always)]
102 pub const fn empty() -> Self {
103 SafeCStrBuilder(Vec::new())
104 }
105
106 #[inline(always)]
112 pub fn push(&mut self, a: u8) -> Result<(), ErrDetectedNullByte> {
113 match NonZeroU8::new(a) {
114 Some(a) => {
115 self.push_nonzero(a);
116
117 Ok(())
118 },
119 None => Err(ErrDetectedNullByte)
120 }
121 }
122
123 #[inline]
125 pub fn push_nonzero(&mut self, a: NonZeroU8) {
126 self.0.push(a.get())
127 }
128
129 #[inline]
131 pub fn is_empty(&self) -> bool {
132 self.0.is_empty()
133 }
134
135 #[inline]
139 #[allow(dead_code)]
140 pub fn as_slice(&self) -> &[u8] {
141 &self.0
142 }
143
144 pub fn extend_from_slice(&mut self, arr: &[u8]) -> Result<(), ErrDetectedNullByte> {
150 match memchr::memchr(0, arr) {
151 Some(..) => Err(ErrDetectedNullByte),
152 None => {
153 self.0.extend_from_slice(arr);
154
155 Ok(())
156 }
157 }
158 }
159
160 pub fn into(mut self) -> Cow<'static, [u8]> {
165 match self.is_empty() {
166 true => {
167 static ECSSTR: &'static [u8] = &[0u8];
169
170 Cow::Borrowed(ECSSTR)
171 },
172 false => {
173 self.0.push(0);
174
175 self.0.into()
176 }
177 }
178 }
179
180 #[inline]
185 pub fn validate_with_fns<R>(
186 &self,
187
188 valid: impl FnOnce() -> R,
189 invalid: impl FnOnce(usize) -> R
190 ) -> R {
191 match memchr::memchr(0, &self.0) {
192 Some(a) => invalid(a),
193 None => valid(),
194 }
195 }
196
197 #[inline]
199 pub fn is_valid(&self) -> bool {
200 self.validate_with_fns(
201 || true, |_| false, )
204 }
205}
206
207#[proc_macro]
222pub fn cstr(token: TokenStream) -> TokenStream {
223 let token = proc_macro2::TokenStream::from(token);
224
225 let mut cstrline = SafeCStrBuilder::empty();
226 if !token.is_empty() {
227 let mut iter = token.into_iter();
228 let mut tree;
229 'main: loop {
230 tree = iter.next();
231
232 'decode: loop {
233 match tree {
234 Some(TokenTree2::Literal(lit)) => { let data = lit.to_string();
236 let bytes = data.as_bytes();
237 let len = bytes.len();
238
239 match len {
240 0 => {}, 1 => { let a = unsafe {
243 debug_assert!({
244 #[allow(clippy::get_first)]
245 bytes.get(0).is_some()
246 });
247
248 bytes.get_unchecked(0) };
250
251 thiserr_nullbyte!(lit, cstrline.push(*a));
252 },
253 len => { let first = unsafe { debug_assert!({
256 #[allow(clippy::get_first)]
257 bytes.get(0).is_some()
258 });
259
260 bytes.get_unchecked(0)
261 };
262 let last = unsafe { debug_assert!(bytes.get(len-1).is_some());
264
265 bytes.get_unchecked(len-1)
266 };
267
268 match (first, last) {
269 (b'"', b'"') => { let arr = unsafe {
271 debug_assert!(bytes.get(1.. len-1).is_some());
272
273 bytes.get_unchecked(1.. len-1) };
275
276 thiserr_nullbyte!(lit, cstrline.extend_from_slice(arr));
277 },
278 (b'b', b'"') if bytes.get(1) == Some(&b'"') => { let arr = unsafe {
280 debug_assert!(bytes.get(1+1.. len-1).is_some());
281
282 bytes.get_unchecked(1+1.. len-1) };
284
285 thiserr_nullbyte!(lit, cstrline.extend_from_slice(arr));
286 },
287 (b'\'', b'\'') => { let arr = unsafe {
289 debug_assert!(bytes.get(1.. len-1).is_some());
290
291 bytes.get_unchecked(1.. len-1) };
293
294 thiserr_nullbyte!(lit, cstrline.extend_from_slice(arr));
295 },
296 (b'b', b'\'') if bytes.get(1) == Some(&b'\'') => { let arr = unsafe {
298 debug_assert!(bytes.get(1+1.. len-1).is_some());
299
300 bytes.get_unchecked(1+1.. len-1) };
302
303 thiserr_nullbyte!(lit, cstrline.extend_from_slice(arr));
304 },
305 (_, _) if bytes.ends_with(b"u8") => { let bytes = unsafe {
307 debug_assert!(bytes.get(.. len-b"u8".len()).is_some());
308
309 bytes.get_unchecked(.. len-b"u8".len()) };
311
312 let num: u8 = match String::from_utf8_lossy(bytes).parse() {
313 Ok(a) => a,
314 Err(..) => {
315 pm_compile_error!(lit.span(), "Input Error");
316 }
317 };
318 thiserr_nullbyte!(lit, cstrline.push(num));
319 },
320 (_, _) if bytes.ends_with(b"i8") => { let bytes = unsafe {
322 debug_assert!(bytes.get(.. len-b"i8".len()).is_some());
323
324 bytes.get_unchecked(.. len-b"i8".len()) };
326
327 let num: i8 = match String::from_utf8_lossy(bytes).parse() {
328 Ok(a) => a,
329 Err(..) => {
330 pm_compile_error!(lit.span(), "Input Error");
331 }
332 };
333 thiserr_nullbyte!(lit, cstrline.push(num as _));
334 },
335 (_, _) => { thiserr_nullbyte!(lit, cstrline.extend_from_slice(bytes));
337 },
338 }
339 }
340 }
341
342 let mut is_en_fatalblock = true;
346 'cparse: loop {
347 tree = iter.next();
348 match tree {
349 None => {
350 break 'main;
351 },
352 Some(TokenTree2::Punct(punct)) if ',' == punct.as_char() => {
353 if !is_en_fatalblock {
354 pm_compile_error!(punct.span(), "Unsupported.")
355 }
356
357 is_en_fatalblock = false;
358 continue 'cparse;
359 },
360
361 Some(..) if !is_en_fatalblock => {
362 continue 'decode;
363 },
364 Some(a_tree) => {
365 pm_compile_error!(a_tree.span(), "It was expected ',' or closing of a macro.")
366 },
367 }
368 }
369 },
370 Some(tk) => {
371 pm_compile_error!(tk.span(), "incorrect data, was expected: &[u8], str, u8, i8, {integer}.");
372 },
373 None => {
374 break 'main;
375 },
376 }
377
378 #[allow(unreachable_code)] {
379 break 'decode;
380 }
381 }
382 }
383 }
384
385 debug_assert!(cstrline.is_valid()); let cstrline = cstrline.into();
387 let arr = &cstrline as &[u8];
388 debug_assert!( CStr::from_bytes_with_nul(arr).is_ok()
390 );
391 let result = Literal::byte_string(arr);
392 let token = quote! {
393 {
394 const _H: &'static CStr = unsafe {
395 &*(#result as *const [u8] as *const CStr) as &'static CStr
396 };
397
398 _H
399 }
400 };
401
402 TokenStream::from(token)
403}