#![no_std]
#![cfg_attr(feature = "nightly", feature(const_fn))]
extern crate failure;
#[cfg(feature = "serde")]
extern crate serde;
use core::cmp::{Ordering, PartialEq, PartialOrd};
use core::convert::AsRef;
use core::fmt::{self, Debug, Display, Formatter};
use core::mem;
use core::num::NonZeroU64;
use core::slice;
use core::str::{self, FromStr};
#[derive(Debug)]
enum ErrorInner {
TooLong,
IllegalChar,
Empty,
}
#[derive(Debug)]
pub struct Error(ErrorInner);
impl failure::Fail for Error {}
impl Display for Error {
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
use self::ErrorInner::*;
f.write_str(match self.0 {
TooLong => "symbol may not exceed 8 bytes",
IllegalChar => "symbol may not contain NUL bytes",
Empty => "symbol may not be zero-length",
})
}
}
#[derive(Copy, Clone, Debug)]
pub struct Bytes(u64);
impl Iterator for Bytes {
type Item = u8;
#[inline]
fn next(&mut self) -> Option<u8> {
let byte = self.0 & 0xff;
if byte == 0 {
None
} else {
self.0 >>= 8;
Some(byte as u8)
}
}
#[inline]
fn count(self) -> usize {
let pad = self.0.leading_zeros() >> 3;
8 - pad as usize
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
let len = self.count();
(len, Some(len))
}
}
impl ExactSizeIterator for Bytes {
#[inline]
fn len(&self) -> usize {
self.count()
}
}
#[cfg(feature = "serde")]
struct ParseVisitor<T>(core::marker::PhantomData<dyn FnOnce() -> T>);
#[cfg(feature = "serde")]
impl<T> ParseVisitor<T> {
#[inline]
pub fn new() -> Self {
ParseVisitor(core::marker::PhantomData)
}
}
#[cfg(feature = "serde")]
impl<'de, T> serde::de::Visitor<'de> for ParseVisitor<T>
where
T: FromStr,
<T as FromStr>::Err: Display,
{
type Value = T;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a string of 8 or fewer non-NUL utf-8 bytes")
}
#[inline]
fn visit_str<E>(self, value: &str) -> Result<T, E>
where
E: serde::de::Error,
{
value.parse().map_err(E::custom)
}
}
#[derive(PartialEq, Eq, Hash, Copy, Clone, PartialOrd, Ord)]
pub struct Printable(NonZeroU64);
impl Debug for Printable {
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
write!(f, "<{}>", self.as_str())
}
}
impl Display for Printable {
#[inline]
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
f.write_str(self.as_ref())
}
}
impl FromStr for Printable {
type Err = Error;
#[inline]
fn from_str(s: &str) -> Result<Self, Error> {
if s.is_empty() {
return Err(Error(ErrorInner::Empty));
}
if s.len() > 8 {
return Err(Error(ErrorInner::TooLong));
}
if s.contains('\0') {
return Err(Error(ErrorInner::IllegalChar));
}
let mut buf = [0u8; 8];
buf[..s.len()].copy_from_slice(s.as_bytes());
unsafe {
Ok(Printable(mem::transmute(buf)))
}
}
}
impl AsRef<[u8]> for Printable {
#[inline]
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl AsRef<str> for Printable {
#[inline]
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl PartialEq<str> for Printable {
#[inline]
fn eq(&self, s: &str) -> bool {
self.as_str() == s
}
}
impl PartialEq<Printable> for str {
#[inline]
fn eq(&self, p: &Printable) -> bool {
self == p.as_str()
}
}
impl PartialOrd<str> for Printable {
#[inline]
fn partial_cmp(&self, s: &str) -> Option<Ordering> {
self.as_str().partial_cmp(s)
}
}
impl PartialOrd<Printable> for str {
#[inline]
fn partial_cmp(&self, p: &Printable) -> Option<Ordering> {
self.partial_cmp(p.as_str())
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for Printable {
#[inline]
fn deserialize<D>(deserializer: D) -> Result<Printable, D::Error>
where
D: serde::de::Deserializer<'de>,
{
deserializer.deserialize_str(ParseVisitor::new())
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for Printable {
#[inline]
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::ser::Serializer,
{
serializer.serialize_str(self.as_str())
}
}
impl From<Lexical> for Printable {
fn from(x: Lexical) -> Self {
x.to_printable()
}
}
#[cfg_attr(feature = "cargo-clippy", allow(len_without_is_empty))]
impl Printable {
#[inline]
fn get(self) -> u64 {
self.0.get()
}
#[inline]
#[cfg(target_endian = "little")]
pub fn len(self) -> usize {
let pad = self.get().leading_zeros() >> 3;
8 - pad as usize
}
#[inline]
#[cfg(target_endian = "big")]
pub fn len(self) -> usize {
let pad = self.get().trailing_zeros() >> 3;
8 - pad as usize
}
#[inline]
pub fn into_bytes(self) -> [u8; 8] {
unsafe { mem::transmute(self.0) }
}
#[cfg_attr(feature = "cargo-clippy", allow(trivially_copy_pass_by_ref))]
#[inline]
pub fn as_ptr(&self) -> *const u8 {
&self.0 as *const NonZeroU64 as *const u8
}
#[cfg_attr(feature = "cargo-clippy", allow(trivially_copy_pass_by_ref))]
#[inline]
pub fn as_bytes(&self) -> &[u8] {
unsafe { slice::from_raw_parts(self.as_ptr(), self.len()) }
}
#[cfg_attr(feature = "cargo-clippy", allow(trivially_copy_pass_by_ref))]
#[inline]
pub fn as_str(&self) -> &str {
unsafe { str::from_utf8_unchecked(self.as_bytes()) }
}
#[inline]
pub fn bytes(self) -> Bytes {
Bytes(self.get())
}
#[inline]
pub fn to_lexical(self) -> Lexical {
Lexical::from_printable(self)
}
}
#[derive(PartialEq, Eq, Hash, Copy, Clone, PartialOrd, Ord)]
pub struct Lexical(NonZeroU64);
impl Debug for Lexical {
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
Debug::fmt(&self.to_printable(), f)
}
}
impl Display for Lexical {
#[inline]
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
Display::fmt(&self.to_printable(), f)
}
}
impl FromStr for Lexical {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Error> {
Printable::from_str(s).map(Printable::to_lexical)
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for Lexical {
#[inline]
fn deserialize<D>(deserializer: D) -> Result<Lexical, D::Error>
where
D: serde::de::Deserializer<'de>,
{
deserializer.deserialize_str(ParseVisitor::new())
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for Lexical {
#[inline]
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::ser::Serializer,
{
serializer.serialize_str(self.to_printable().as_str())
}
}
impl From<Printable> for Lexical {
fn from(x: Printable) -> Self {
x.to_lexical()
}
}
#[cfg_attr(feature = "cargo-clippy", allow(len_without_is_empty))]
impl Lexical {
#[inline]
fn get(self) -> u64 {
self.0.get()
}
#[inline]
pub fn len(self) -> usize {
let pad = self.get().trailing_zeros() >> 3;
8 - pad as usize
}
#[inline]
pub fn into_bytes(self) -> [u8; 8] {
self.to_printable().into_bytes()
}
#[inline]
pub fn bytes(self) -> Bytes {
self.to_printable().bytes()
}
#[inline]
fn from_printable(x: Printable) -> Self {
Lexical(unsafe { NonZeroU64::new_unchecked(u64::from_be(x.get())) })
}
#[inline]
pub fn to_printable(self) -> Printable {
Printable(unsafe { NonZeroU64::new_unchecked(u64::to_be(self.get())) })
}
}
pub type Symbol = Lexical;
#[cfg(feature = "nightly")]
#[cfg_attr(feature = "cargo-clippy", allow(cast_lossless))]
const fn to_int_be(s: &[u8]) -> u64 {
s[0] as u64
+ ((s[((s.len() > 1) as usize)] as u64 * ((s.len() > 1) as u64)) << 8)
+ ((s[2 * ((s.len() > 2) as usize)] as u64 * ((s.len() > 2) as u64)) << 16)
+ ((s[3 * ((s.len() > 3) as usize)] as u64 * ((s.len() > 3) as u64)) << 24)
+ ((s[4 * ((s.len() > 4) as usize)] as u64 * ((s.len() > 4) as u64)) << 32)
+ ((s[5 * ((s.len() > 5) as usize)] as u64 * ((s.len() > 5) as u64)) << 40)
+ ((s[6 * ((s.len() > 6) as usize)] as u64 * ((s.len() > 6) as u64)) << 48)
+ ((s[7 * ((s.len() > 7) as usize)] as u64 * ((s.len() > 7) as u64)) << 56)
}
#[cfg(feature = "nightly")]
#[cfg_attr(feature = "cargo-clippy", allow(cast_lossless))]
const fn to_int_le(s: &[u8]) -> u64 {
((s[0] as u64) << 56)
+ ((s[((s.len() > 1) as usize)] as u64 * ((s.len() > 1) as u64)) << 48)
+ ((s[2 * ((s.len() > 2) as usize)] as u64 * ((s.len() > 2) as u64)) << 40)
+ ((s[3 * ((s.len() > 3) as usize)] as u64 * ((s.len() > 3) as u64)) << 32)
+ ((s[4 * ((s.len() > 4) as usize)] as u64 * ((s.len() > 4) as u64)) << 24)
+ ((s[5 * ((s.len() > 5) as usize)] as u64 * ((s.len() > 5) as u64)) << 16)
+ ((s[6 * ((s.len() > 6) as usize)] as u64 * ((s.len() > 6) as u64)) << 8)
+ (s[7 * ((s.len() > 7) as usize)] as u64 * ((s.len() > 7) as u64))
}
#[cfg(all(feature = "nightly", endian = "little"))]
const fn to_int_native(s: &[u8]) {
to_int_le(s)
}
#[cfg(all(feature = "nightly", endian = "big"))]
const fn to_int_native(s: &[u8]) {
to_int_be(s)
}
#[cfg(feature = "nightly")]
#[cfg_attr(feature = "cargo-clippy", allow(unnecessary_operation))]
const fn validate(s: &[u8]) {
[()][(s.len() == 0) as usize]; [()][(s.len() > 8) as usize];
}
#[cfg(feature = "nightly")]
pub const fn printable(s: &str) -> Printable {
let s = s.as_bytes();
validate(s);
let n = to_int_be(s);
Printable(unsafe { NonZeroU64::new_unchecked(n) })
}
#[cfg(feature = "nightly")]
pub const fn lexical(s: &str) -> Lexical {
let s = s.as_bytes();
validate(s);
let n = to_int_le(s);
Lexical(unsafe { NonZeroU64::new_unchecked(n) })
}
#[cfg(feature = "nightly")]
pub const fn symbol(s: &str) -> Lexical {
lexical(s)
}
#[cfg(test)]
mod tests {
extern crate std;
use super::Lexical;
use super::Printable;
use core::str::FromStr;
#[test]
fn test_bytes() {
let foo: Lexical = "foo".parse().unwrap();
let bar: std::vec::Vec<_> = foo.bytes().collect();
assert_eq!(&bar, &['f' as u8, 'o' as u8, 'o' as u8]);
}
#[test]
fn test_len() {
assert_eq!(Lexical::from_str("somestr").unwrap().len(), 7);
assert_eq!(Printable::from_str("somestr").unwrap().len(), 7);
}
#[test]
fn test_round_trip() {
let fool: Lexical = "foo".parse().unwrap();
let foop: Printable = "foo".parse().unwrap();
assert_eq!(fool.to_printable(), foop);
assert_eq!(fool, foop.to_lexical());
}
#[test]
fn test_into_bytes() {
let array = ['f' as u8, 'o' as u8, 'o' as u8, 0u8, 0u8, 0u8, 0u8, 0u8];
assert_eq!(Lexical::from_str("foo").unwrap().into_bytes(), array);
assert_eq!(Printable::from_str("foo").unwrap().into_bytes(), array);
}
}