#![no_std]
#[cfg(test)]
mod tests;
use core::mem::{
ManuallyDrop,
MaybeUninit
};
#[macro_export]
macro_rules! construe {
($v:vis const $name:ident: & $($l:lifetime)? str = $f:expr) => {
$v const $name: &$($l)? str = $crate::construe!(&str => $f);
};
($v:vis const $name:ident: & $($l:lifetime)? [$t:ty] = $f:expr) => {
$v const $name: & $($l)? [$t] = &$crate::construe!([$t; _] => $f);
};
($v:vis const $name:ident: [$t:ty; _] = $f:expr) => {
$v const $name: [$t; {$f.needs_len()}] = $f.finish();
};
(&str => $f:expr) => {{
const ARRAY: [u8; {$f.needs_len()}] = $f.store_bytes();
$crate::StrConstrue::borrow_str(&ARRAY)
}};
(&[$t:ty] => $f:expr) => {{
const ARRAY: [$t; {$f.needs_len()}] = $f.finish();
&ARRAY
}};
([$t:ty; _] => $f:expr) => {{
const ARRAY: [$t; {$f.needs_len()}] = $f.finish();
ARRAY
}};
}
#[macro_export]
macro_rules! write {
($sc:ident, $( $v:expr ),* $(,)?) => {
$( $sc = $crate::Display($v).write($sc); )*
};
}
pub struct Construe<T, const N: usize> {
offset: usize,
buffer: [MaybeUninit<T>; N]
}
pub struct StrConstrue<const N: usize>(Construe<u8, N>);
pub struct Display<T>(pub T);
union ArrayTranspose<T, const N: usize> {
complete: ManuallyDrop<MaybeUninit<[T; N]>>,
elements: ManuallyDrop<[MaybeUninit<T>; N]>
}
impl<T, const N: usize> ArrayTranspose<T, N> {
const fn elements(array: MaybeUninit<[T; N]>) -> [MaybeUninit<T>; N] {
let transpose = Self {
complete: ManuallyDrop::new(array)
};
unsafe {ManuallyDrop::into_inner(transpose.elements)}
}
const fn complete(array: [MaybeUninit<T>; N]) -> MaybeUninit<[T; N]> {
let transpose = Self {
elements: ManuallyDrop::new(array)
};
unsafe {ManuallyDrop::into_inner(transpose.complete)}
}
}
impl<T> Construe<T, 0> {
pub const fn start() -> Self {
Self { offset: 0, buffer: [] }
}
pub const fn needs_len(&self) -> usize {
self.offset
}
}
impl<T, const N: usize> Construe<T, N> {
pub const fn new() -> Self {
Self {
offset: 0,
buffer: ArrayTranspose::elements(MaybeUninit::uninit())
}
}
pub const fn len(&self) -> usize {
self.offset
}
pub const fn is_empty(&self) -> bool {
self.len() == 0
}
#[must_use]
pub const fn push(mut self, item: T) -> (Self, Option<T>) {
let item = match N {
0 => Some(item),
_ => {
self.buffer[self.offset] = MaybeUninit::new(item);
None
}
};
self.offset += 1;
(self, item)
}
pub const unsafe fn steal_from_slice(mut self, slice: &[T]) -> Self {
assert!(
!core::mem::needs_drop::<T>(),
"may not steal from slices of types that need to be dropped"
);
if N == 0 {
self.offset += slice.len();
}
else {
assert!(self.offset + slice.len() < N, "buffer overflow");
let mut i = 0;
let len = slice.len();
let item_ptr = slice.as_ptr();
while i < len {
let item = unsafe { item_ptr.add(i).read() };
self.buffer[self.offset + i] = MaybeUninit::new(item);
i += 1;
}
self.offset += i;
}
self
}
pub const fn finish(self) -> [T; N] {
assert!(
N == self.offset || N == 0,
"tried to extract partially filled buffer"
);
assert!(
N == self.offset || N != 0,
"tried to extract zero-length buffer that pretended to store items"
);
unsafe {ArrayTranspose::complete(self.buffer).assume_init()}
}
}
impl<T: Copy, const N: usize> Construe<T, N> {
pub const fn copy_from(mut self, mut slice: &[T]) -> Self {
if N == 0 {
self.offset += slice.len();
}
else {
while let [byte, rest @ ..] = slice {
slice = rest;
self.buffer[self.offset] = MaybeUninit::new(*byte);
self.offset += 1;
}
}
self
}
}
impl<const N: usize> StrConstrue<N> {
pub const fn new() -> Self {Self(Construe::new())}
pub const fn len(&self) -> usize {self.0.len()}
pub const fn is_empty(&self) -> bool {self.0.is_empty()}
#[must_use]
pub const fn push_str(self, s: &str) -> Self {
Self(self.0.copy_from(s.as_bytes()))
}
const fn push_byte(self, byte: u8) -> Self {
match core::str::from_utf8(&[byte]) {
Ok(as_str) => self.push_str(as_str),
Err(_e) => panic!("byte is invalid UTF-8")
}
}
pub const fn store_bytes(self) -> [u8; N] {
self.0.finish()
}
}
impl StrConstrue<0> {
pub const fn needs_len(&self) -> usize {self.0.needs_len()}
pub const fn borrow_str(byte_slice: &[u8]) -> &str {
match core::str::from_utf8(byte_slice) {
Ok(slice_as_str) => slice_as_str,
Err(_e) => panic!("assembled byte slice contains invalid UTF-8")
}
}
}
macro_rules! impl_write_unsigned_int {
($i:ty) => {
impl Display<$i> {
pub const fn write<const N: usize>(self, mut sc: StrConstrue<N>)
-> StrConstrue<N>
{
let int = self.0;
let mut digits = match int.checked_ilog10() {
Some(d) => d,
None => 0
};
loop {
let digit = (int / <$i>::pow(10, digits)) % 10;
sc = sc.push_byte(b'0' + digit as u8);
digits = match digits.checked_sub(1) {
Some(d) => d,
None => break
};
}
sc
}
}
};
( $($i:ty),* ) => {
$( impl_write_unsigned_int!($i); )*
};
}
macro_rules! impl_write_signed_int {
($i:ty) => {
impl Display<$i> {
pub const fn write<const N: usize>(self, mut sc: StrConstrue<N>)
-> StrConstrue<N>
{
let int = self.0;
if int < 0 {
sc = sc.push_str("-");
}
let mut digits = match int.checked_ilog10() {
Some(d) => d,
None if int == 0 => 0,
None if int == <$i>::MIN => <$i>::MAX.ilog10(),
None => int.abs().ilog10()
};
loop {
let digit = (int / <$i>::pow(10, digits)) % 10;
sc = sc.push_byte(b'0' + digit.abs() as u8);
digits = match digits.checked_sub(1) {
Some(d) => d,
None => break
};
}
sc
}
}
};
( $($i:ty),* ) => {
$( impl_write_signed_int!($i); )*
};
}
impl_write_unsigned_int!(u8, u16, u32, u64, u128, usize);
impl_write_signed_int!(i8, i16, i32, i64, i128, isize);
macro_rules! impl_write_nonzero_int {
($i:ty) => {
impl Display<$i> {
pub const fn write<const N: usize>(self, sc: StrConstrue<N>)
-> StrConstrue<N>
{
Display(self.0.get()).write(sc)
}
}
};
( $($i:ty),* ) => {
$( impl_write_nonzero_int!($i); )*
};
}
impl_write_nonzero_int!(
core::num::NonZeroI8,
core::num::NonZeroI16,
core::num::NonZeroI32,
core::num::NonZeroI64,
core::num::NonZeroI128,
core::num::NonZeroIsize,
core::num::NonZeroU8,
core::num::NonZeroU16,
core::num::NonZeroU32,
core::num::NonZeroU64,
core::num::NonZeroU128,
core::num::NonZeroUsize
);
impl Display<char> {
pub const fn write<const N: usize>(self, sc: StrConstrue<N>)
-> StrConstrue<N>
{
let (array, len) = encode_utf8_raw(self.0);
let slice = array.as_slice().split_at(4 - len).1;
let Ok(s) = core::str::from_utf8(slice) else {unreachable!()};
sc.push_str(s)
}
}
impl Display<&str> {
pub const fn write<const N: usize>(self, sc: StrConstrue<N>)
-> StrConstrue<N>
{
sc.push_str(self.0)
}
}
const fn encode_utf8_raw(ch: char) -> ([u8; 4], usize) {
const TAG_CONT: u8 = 0b1000_0000;
const TAG_TWO_B: u8 = 0b1100_0000;
const TAG_THREE_B: u8 = 0b1110_0000;
const TAG_FOUR_B: u8 = 0b1111_0000;
let len = ch.len_utf8();
let code = ch as u32;
let array = match len {
1 => [0, 0, 0, code as u8],
2 => [
0,
0,
(code >> 6 & 0x1F) as u8 | TAG_TWO_B,
(code & 0x3F) as u8 | TAG_CONT,
],
3 => [
0,
(code >> 12 & 0x0F) as u8 | TAG_THREE_B,
(code >> 6 & 0x3F) as u8 | TAG_CONT,
(code & 0x3F) as u8 | TAG_CONT,
],
4 => [
(code >> 18 & 0x07) as u8 | TAG_FOUR_B,
(code >> 12 & 0x3F) as u8 | TAG_CONT,
(code >> 6 & 0x3F) as u8 | TAG_CONT,
(code & 0x3F) as u8 | TAG_CONT,
],
_ => unreachable!()
};
(array, len)
}