#![allow(rustdoc::redundant_explicit_links)] #![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(do_doc_cfg, feature(doc_cfg))]
mod gen;
mod utf8;
#[cfg(feature = "macros")]
#[cfg_attr(do_doc_cfg, doc(cfg(feature = "macros")))]
pub use typeslice_macros::from_bytes;
#[cfg(feature = "macros")]
#[cfg_attr(do_doc_cfg, doc(cfg(feature = "macros")))]
pub use typeslice_macros::from_str;
#[cfg(feature = "macros")]
#[cfg_attr(do_doc_cfg, doc(cfg(feature = "macros")))]
pub use typeslice_macros::utf8;
pub trait TypeSlice<T: 'static> {
const LIST: List<'static, T>;
const LEN: usize;
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum List<'a, T> {
Item { head: &'a T, rest: &'a Self },
Empty,
}
impl<'a, T> Clone for List<'a, T> {
fn clone(&self) -> Self {
*self
}
}
impl<'a, T> Copy for List<'a, T> {}
impl<'a, T> Default for List<'a, T> {
fn default() -> Self {
Self::new()
}
}
impl<'a, T> List<'a, T> {
pub const fn new() -> Self {
Self::Empty
}
pub const fn len(&self) -> usize {
match self {
List::Item {
head: _,
rest: next,
} => 1 + next.len(),
List::Empty => 0,
}
}
pub const fn get(&self, ix: usize) -> Option<&T> {
match (self, ix) {
(Self::Empty, _) => None,
(Self::Item { head, rest: _ }, 0) => Some(head),
(
Self::Item {
head: _,
rest: next,
},
_,
) => match ix.checked_sub(1) {
Some(nix) => next.get(nix),
None => None,
},
}
}
pub const fn is_empty(&self) -> bool {
matches!(self, Self::Empty)
}
pub const fn into_option(self) -> Option<(&'a T, &'a Self)> {
match self {
List::Item { head, rest: next } => Some((head, next)),
List::Empty => None,
}
}
pub const fn iter(&self) -> Iter<'a, T> {
Iter { inner: *self }
}
}
impl<'a, T> IntoIterator for List<'a, T> {
type Item = &'a T;
type IntoIter = Iter<'a, T>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
pub struct Iter<'a, T> {
inner: List<'a, T>,
}
impl<'a, T> Iterator for Iter<'a, T> {
type Item = &'a T;
fn next(&mut self) -> Option<Self::Item> {
match self.inner.into_option() {
Some((t, next)) => {
self.inner = *next;
Some(t)
}
None => None,
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.inner.len(), Some(self.inner.len()))
}
}
impl<'a, T> core::ops::Index<usize> for List<'a, T> {
type Output = T;
fn index(&self, index: usize) -> &Self::Output {
match self.get(index) {
Some(it) => it,
None => {
panic!(
"index out of bounds: the len is {} but the index is {}",
self.len(),
index
)
}
}
}
}
macro_rules! next_in_list {
($ident:ident) => {
match List::into_option(*$ident) {
Some((t, next)) => {
$ident = next;
Some(*t)
}
None => None,
}
};
}
impl<'a> List<'a, char> {
pub const fn str_eq(&self, s: &str) -> bool {
use crate::utf8::Pop;
let mut us = self;
let mut them = s.as_bytes();
loop {
match (next_in_list!(us), utf8::pop(them)) {
(Some(ours), Pop::Ok(theirs)) => match ours == theirs {
true => {
let (_popped, next) = them.split_at(ours.len_utf8());
them = next;
continue;
}
false => return false, },
(None, Pop::Empty) => return true, (_, Pop::Invalid | Pop::Truncated) => panic!("invalid utf8-8"),
(None, Pop::Ok(_)) | (Some(_), Pop::Empty) => return false, }
}
}
}
pub mod types {
use crate::{List, TypeSlice};
use core::marker::PhantomData;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
enum Never {}
#[rustfmt::skip]
macro_rules! for_all_const_types {
($do:ident) => {
$do!(Usize/UsizeNil for usize); $do!(U8/U8Nil for u8); $do!(U16/U16Nil for u16); $do!(U32/U32Nil for u32); $do!(U64/U64Nil for u64); $do!(U128/U128Nil for u128);
$do!(Isize/IsizeNil for isize); $do!(I8/I8Nil for i8); $do!(I16/I16Nil for i16); $do!(I32/I32Nil for i32); $do!(I64/I64Nil for i64); $do!(I128/I128Nil for i128);
$do!(Char/CharNil for char);
$do!(Bool/BoolNil for bool);
};
}
macro_rules! impl_slice_eq {
($name:ident/$nil:ident for $ty:ty) => {
impl List<'_, $ty> {
pub const fn slice_eq(&self, slice: &[$ty]) -> bool {
if self.len() != slice.len() {
return false;
}
let mut ix = slice.len();
while let Some(nix) = ix.checked_sub(1) {
let Some(ours) = self.get(nix) else {
unreachable!()
};
if *ours != slice[nix] {
return false;
}
ix = nix
}
true
}
}
};
}
for_all_const_types!(impl_slice_eq);
macro_rules! define {
($name:ident/$nil:ident for $ty:ty) => {
#[doc = stringify!($ty)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct $name<const ELEM: $ty, Rest> {
_never: Never,
_phantom: PhantomData<fn() -> Rest>,
}
#[doc = stringify!($ty)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum $nil {}
impl<const ELEM: $ty, Rest: TypeSlice<$ty>> TypeSlice<$ty> for $name<ELEM, Rest> {
const LIST: List<'static, $ty> = List::Item {
head: &ELEM,
rest: &Rest::LIST,
};
const LEN: usize = 1 + Rest::LEN;
}
impl TypeSlice<$ty> for $nil {
const LIST: List<'static, $ty> = List::Empty;
const LEN: usize = 0;
}
};
}
for_all_const_types!(define);
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::*;
use static_assertions::{const_assert, const_assert_eq};
type Empty = U8Nil;
type Hello = U8<b'h', U8<b'e', U8<b'l', U8<b'l', U8<b'o', U8Nil>>>>>;
const_assert!(Empty::LIST.slice_eq(b""));
const_assert_eq!(Empty::LEN, 0);
const_assert!(Hello::LIST.slice_eq(b"hello"));
const_assert_eq!(Hello::LEN, 5);
type Empty2 = u8![];
type Hello2 = u8![0x68, 0x65, 0x6c, 0x6c, 0x6f];
const_assert!(Empty2::LIST.slice_eq(b""));
const_assert_eq!(Empty2::LEN, 0);
const_assert!(Hello2::LIST.slice_eq(b"hello"));
const_assert_eq!(Hello2::LEN, 5);
#[test]
fn test() {
itertools::assert_equal(Empty::LIST, b"");
itertools::assert_equal(Hello::LIST, b"hello");
itertools::assert_equal(Empty2::LIST, b"");
itertools::assert_equal(Hello2::LIST, b"hello");
}
#[cfg(feature = "std")]
#[test]
fn gen() {
const TEMPLATE: &str = r##"
/// Define a type-level [`TypeSlice`](crate::TypeSlice) of [`~prim~`]s.
/// ```
/// # use typeslice::TypeSlice as _;
/// type ~example_ty~ = typeslice::~prim~![~example_lit~];
/// assert!(~example_ty~::LIST.slice_eq(&[~example_lit~]))
/// ```
#[macro_export]
macro_rules! ~prim~ {
() => {
$crate::types::~nil~
};
($first:literal $(,$rest:tt)* $(,)?) => {
$crate::types::~ty~<$first, $crate::~prim~!($($rest,)*)>
}
}
"##;
let mut expected = String::from("// this file is @generated in typestr's tests\n\n");
let numeric_ty = "OneTwoThree";
let numeric_lit = "1, 2, 3";
for (ty, nil, prim, example_ty, example_lit) in [
("Usize", "UsizeNil", "usize", numeric_ty, numeric_lit),
("U8", "U8Nil", "u8", numeric_ty, numeric_lit),
("U16", "U16Nil", "u16", numeric_ty, numeric_lit),
("U32", "U32Nil", "u32", numeric_ty, numeric_lit),
("U64", "U64Nil", "u64", numeric_ty, numeric_lit),
("U128", "U128Nil", "u128", numeric_ty, numeric_lit),
("Isize", "IsizeNil", "isize", numeric_ty, numeric_lit),
("I8", "I8Nil", "i8", numeric_ty, numeric_lit),
("I16", "I16Nil", "i16", numeric_ty, numeric_lit),
("I32", "I32Nil", "i32", numeric_ty, numeric_lit),
("I64", "I64Nil", "i64", numeric_ty, numeric_lit),
("I128", "I128Nil", "i128", numeric_ty, numeric_lit),
("Char", "CharNil", "char", "Abc", "'a', 'b', 'c'"),
("Bool", "BoolNil", "bool", "TrueFalse", "true, false"),
] {
expected.push_str(
&TEMPLATE
.trim_start()
.replace("~ty~", ty)
.replace("~nil~", nil)
.replace("~prim~", prim)
.replace("~example_ty~", example_ty)
.replace("~example_lit~", example_lit),
);
}
let path = concat!(env!("CARGO_MANIFEST_DIR"), "/src/gen.rs");
match std::fs::read_to_string(path) {
Ok(actual) if expected == actual => return, _ => {}
}
let _ = std::fs::write(
concat!(env!("CARGO_MANIFEST_DIR"), "/src/gen.rs.expected"),
expected,
);
panic!("generated file does not match")
}
#[cfg(feature = "std")]
#[test]
fn readme() {
assert!(
std::process::Command::new("cargo")
.args([
"rdme",
"--check",
"--no-fail-on-warnings" ])
.output()
.expect("couldn't run `cargo rdme`")
.status
.success(),
"README.md is out of date - bless the new version by running `cargo rdme`"
)
}
}