#![allow(non_snake_case)]
#![allow(clippy::redundant_static_lifetimes)]
#![allow(clippy::tabs_in_doc_comments)]
#![allow(clippy::needless_doctest_main)]
#![no_std]
extern crate proc_macro;
extern crate alloc;
use core::ffi::CStr;
use alloc::borrow::Cow;
use alloc::string::{String, ToString};
use core::num::NonZeroU8;
use alloc::vec::Vec;
use quote::quote;
use proc_macro::TokenStream;
use proc_macro2::{TokenTree as TokenTree2, Literal, Span};
#[inline]
fn __make_pm_compile_error(span: Span, message: &str) -> TokenStream {
TokenStream::from(quote::quote_spanned! {
span =>
compile_error! { #message }
})
}
macro_rules! pm_compile_error {
($span: expr, $e: expr) => {{
return __make_pm_compile_error($span, $e);
}};
}
macro_rules! thiserr_nullbyte {
[
$lit: ident, $e: expr $(,)?
] => {{
let e: Result<(), ErrDetectedNullByte> = $e;
if e.is_err() {
pm_compile_error!($lit.span(), "Format convention error, null byte detected.");
}
}};
}
struct ErrDetectedNullByte;
struct SafeCStrBuilder(Vec<u8>);
impl SafeCStrBuilder {
#[inline(always)]
pub const fn empty() -> Self {
SafeCStrBuilder(Vec::new())
}
#[inline(always)]
pub fn push(&mut self, a: u8) -> Result<(), ErrDetectedNullByte> {
match NonZeroU8::new(a) {
Some(a) => {
self.push_nonzero(a);
Ok(())
},
None => Err(ErrDetectedNullByte)
}
}
#[inline]
pub fn push_nonzero(&mut self, a: NonZeroU8) {
self.0.push(a.get())
}
#[inline]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
#[inline]
#[allow(dead_code)]
pub fn as_slice(&self) -> &[u8] {
&self.0
}
pub fn extend_from_slice(&mut self, arr: &[u8]) -> Result<(), ErrDetectedNullByte> {
match memchr::memchr(0, arr) {
Some(..) => Err(ErrDetectedNullByte),
None => {
self.0.extend_from_slice(arr);
Ok(())
}
}
}
pub fn into(mut self) -> Cow<'static, [u8]> {
match self.is_empty() {
true => {
static ECSSTR: &'static [u8] = &[0u8];
Cow::Borrowed(ECSSTR)
},
false => {
self.0.push(0);
self.0.into()
}
}
}
#[inline]
pub fn validate_with_fns<R>(
&self,
valid: impl FnOnce() -> R,
invalid: impl FnOnce(usize) -> R
) -> R {
match memchr::memchr(0, &self.0) {
Some(a) => invalid(a),
None => valid(),
}
}
#[inline]
pub fn is_valid(&self) -> bool {
self.validate_with_fns(
|| true, |_| false, )
}
}
#[proc_macro]
pub fn cstr(token: TokenStream) -> TokenStream {
let token = proc_macro2::TokenStream::from(token);
let mut cstrline = SafeCStrBuilder::empty();
if !token.is_empty() {
let mut iter = token.into_iter();
let mut tree;
'main: loop {
tree = iter.next();
'decode: loop {
match tree {
Some(TokenTree2::Literal(lit)) => { let data = lit.to_string();
let bytes = data.as_bytes();
let len = bytes.len();
match len {
0 => {}, 1 => { let a = unsafe {
debug_assert!({
#[allow(clippy::get_first)]
bytes.get(0).is_some()
});
bytes.get_unchecked(0) };
thiserr_nullbyte!(lit, cstrline.push(*a));
},
len => { let first = unsafe { debug_assert!({
#[allow(clippy::get_first)]
bytes.get(0).is_some()
});
bytes.get_unchecked(0)
};
let last = unsafe { debug_assert!(bytes.get(len-1).is_some());
bytes.get_unchecked(len-1)
};
match (first, last) {
(b'"', b'"') => { let arr = unsafe {
debug_assert!(bytes.get(1.. len-1).is_some());
bytes.get_unchecked(1.. len-1) };
thiserr_nullbyte!(lit, cstrline.extend_from_slice(arr));
},
(b'b', b'"') if bytes.get(1) == Some(&b'"') => { let arr = unsafe {
debug_assert!(bytes.get(1+1.. len-1).is_some());
bytes.get_unchecked(1+1.. len-1) };
thiserr_nullbyte!(lit, cstrline.extend_from_slice(arr));
},
(b'\'', b'\'') => { let arr = unsafe {
debug_assert!(bytes.get(1.. len-1).is_some());
bytes.get_unchecked(1.. len-1) };
thiserr_nullbyte!(lit, cstrline.extend_from_slice(arr));
},
(b'b', b'\'') if bytes.get(1) == Some(&b'\'') => { let arr = unsafe {
debug_assert!(bytes.get(1+1.. len-1).is_some());
bytes.get_unchecked(1+1.. len-1) };
thiserr_nullbyte!(lit, cstrline.extend_from_slice(arr));
},
(_, _) if bytes.ends_with(b"u8") => { let bytes = unsafe {
debug_assert!(bytes.get(.. len-b"u8".len()).is_some());
bytes.get_unchecked(.. len-b"u8".len()) };
let num: u8 = match String::from_utf8_lossy(bytes).parse() {
Ok(a) => a,
Err(..) => {
pm_compile_error!(lit.span(), "Input Error");
}
};
thiserr_nullbyte!(lit, cstrline.push(num));
},
(_, _) if bytes.ends_with(b"i8") => { let bytes = unsafe {
debug_assert!(bytes.get(.. len-b"i8".len()).is_some());
bytes.get_unchecked(.. len-b"i8".len()) };
let num: i8 = match String::from_utf8_lossy(bytes).parse() {
Ok(a) => a,
Err(..) => {
pm_compile_error!(lit.span(), "Input Error");
}
};
thiserr_nullbyte!(lit, cstrline.push(num as _));
},
(_, _) => { thiserr_nullbyte!(lit, cstrline.extend_from_slice(bytes));
},
}
}
}
let mut is_en_fatalblock = true;
'cparse: loop {
tree = iter.next();
match tree {
None => {
break 'main;
},
Some(TokenTree2::Punct(punct)) if ',' == punct.as_char() => {
if !is_en_fatalblock {
pm_compile_error!(punct.span(), "Unsupported.")
}
is_en_fatalblock = false;
continue 'cparse;
},
Some(..) if !is_en_fatalblock => {
continue 'decode;
},
Some(a_tree) => {
pm_compile_error!(a_tree.span(), "It was expected ',' or closing of a macro.")
},
}
}
},
Some(tk) => {
pm_compile_error!(tk.span(), "incorrect data, was expected: &[u8], str, u8, i8, {integer}.");
},
None => {
break 'main;
},
}
#[allow(unreachable_code)] {
break 'decode;
}
}
}
}
debug_assert!(cstrline.is_valid()); let cstrline = cstrline.into();
let arr = &cstrline as &[u8];
debug_assert!( CStr::from_bytes_with_nul(arr).is_ok()
);
let result = Literal::byte_string(arr);
let token = quote! {
{
const _H: &'static CStr = unsafe {
&*(#result as *const [u8] as *const CStr) as &'static CStr
};
_H
}
};
TokenStream::from(token)
}