diff --git a/crates/fayalite/src/util.rs b/crates/fayalite/src/util.rs index dc20513..ed5c05d 100644 --- a/crates/fayalite/src/util.rs +++ b/crates/fayalite/src/util.rs @@ -1,267 +1,20 @@ // SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information -use crate::intern::{Intern, Interned}; -use bitvec::{bits, order::Lsb0, slice::BitSlice, view::BitView}; -use std::{ - cmp::Ordering, - fmt::{self, Debug, Write}, - hash::Hash, - mem::ManuallyDrop, - ptr, - rc::Rc, - sync::{Arc, OnceLock}, + +mod const_bool; +mod const_cmp; +mod misc; + +#[doc(inline)] +pub use const_bool::{ConstBool, ConstBoolDispatch, ConstBoolDispatchTag, GenericConstBool}; + +#[doc(inline)] +pub use const_cmp::{ + const_bytes_cmp, const_str_array_is_strictly_ascending, const_str_cmp, const_u8_cmp, + const_usize_cmp, }; -mod sealed { - pub trait Sealed {} -} - -/// # Safety -/// the only implementation is `ConstBool` -pub unsafe trait GenericConstBool: - sealed::Sealed + Copy + Ord + Hash + Default + Debug + 'static + Send + Sync -{ - const VALUE: bool; -} - -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] -pub struct ConstBool; - -impl Debug for ConstBool { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_tuple("ConstBool").field(&Self::VALUE).finish() - } -} - -impl sealed::Sealed for ConstBool {} - -/// SAFETY: the only implementation is `ConstBool` -unsafe impl GenericConstBool for ConstBool { - const VALUE: bool = VALUE; -} - -pub trait ConstBoolDispatchTag { - type Type; -} - -pub enum ConstBoolDispatch { - False(FalseT), - True(TrueT), -} - -impl ConstBoolDispatch { - pub fn new< - Tag: ConstBoolDispatchTag> = FalseT> - + ConstBoolDispatchTag> = TrueT>, - Select: GenericConstBool, - >( - v: Tag::Type = &*v; - // SAFETY: reads the exact same type, since Select is really ConstBool - unsafe { - if Select::VALUE { - ConstBoolDispatch::True(ptr::read(v_ptr.cast())) - } else { - ConstBoolDispatch::False(ptr::read(v_ptr.cast())) - } - } - } -} - -/// replacement for Iterator::eq_by which isn't stable yet -pub fn iter_eq_by bool>( - l: L, - r: R, - mut f: F, -) -> bool { - let mut l = l.into_iter(); - let mut r = r.into_iter(); - l.try_for_each(|l| { - if let Some(r) = r.next() { - f(l, r).then_some(()) - } else { - None - } - }) - .is_some() - && r.next().is_none() -} - -pub struct DebugAsDisplay(pub T); - -impl fmt::Debug for DebugAsDisplay { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} - -pub const fn const_u8_cmp(a: u8, b: u8) -> Ordering { - if a < b { - Ordering::Less - } else if a > b { - Ordering::Greater - } else { - Ordering::Equal - } -} - -pub const fn const_usize_cmp(a: usize, b: usize) -> Ordering { - if a < b { - Ordering::Less - } else if a > b { - Ordering::Greater - } else { - Ordering::Equal - } -} - -pub const fn const_bytes_cmp(a: &[u8], b: &[u8]) -> Ordering { - let mut i = 0; - while i < a.len() && i < b.len() { - match const_u8_cmp(a[i], b[i]) { - Ordering::Equal => {} - retval => return retval, - } - i += 1; - } - const_usize_cmp(a.len(), b.len()) -} - -pub const fn const_str_cmp(a: &str, b: &str) -> Ordering { - const_bytes_cmp(a.as_bytes(), b.as_bytes()) -} - -pub const fn const_str_array_is_strictly_ascending(a: &[&str]) -> bool { - let mut i = 1; - while i < a.len() { - match const_str_cmp(a[i - 1], a[i]) { - Ordering::Less => {} - _ => return false, - } - i += 1; - } - true -} - -pub trait MakeMutSlice { - type Element: Clone; - fn make_mut_slice(&mut self) -> &mut [Self::Element]; -} - -impl MakeMutSlice for Arc<[T]> { - type Element = T; - - fn make_mut_slice(&mut self) -> &mut [Self::Element] { - if Arc::get_mut(self).is_none() { - *self = Arc::<[T]>::from(&**self); - } - Arc::get_mut(self).unwrap() - } -} - -impl MakeMutSlice for Rc<[T]> { - type Element = T; - - fn make_mut_slice(&mut self) -> &mut [Self::Element] { - if Rc::get_mut(self).is_none() { - *self = Rc::<[T]>::from(&**self); - } - Rc::get_mut(self).unwrap() - } -} - -pub struct DebugAsRawString<'a>(pub &'a str); - -impl fmt::Debug for DebugAsRawString<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let pounds = self - .0 - .split_inclusive('"') - .skip(1) - .map(|v| v.len() - v.trim_start_matches('#').len()) - .max() - .map(|v| v + 1) - .unwrap_or(0); - f.write_char('r')?; - for _ in 0..pounds { - f.write_char('#')?; - } - f.write_char('"')?; - f.write_str(self.0)?; - f.write_char('"')?; - for _ in 0..pounds { - f.write_char('#')?; - } - Ok(()) - } -} - -pub fn interned_bit(v: bool) -> Interned { - static RETVAL: OnceLock<[Interned; 2]> = OnceLock::new(); - // use bits![V; 1] instead of bits![V] to work around https://github.com/ferrilab/bitvec/issues/271 - RETVAL.get_or_init(|| [bits![0; 1].intern(), bits![1; 1].intern()])[v as usize] -} - -#[derive(Copy, Clone, Debug)] -pub struct BitSliceWriteWithBase<'a>(pub &'a BitSlice); - -impl BitSliceWriteWithBase<'_> { - fn fmt_with_base( - self, - f: &mut fmt::Formatter<'_>, - ) -> fmt::Result { - let digit_count = self.0.len().div_ceil(BITS_PER_DIGIT).max(1); - // TODO: optimize - let mut buf = String::with_capacity(digit_count); - for digit_index in (0..digit_count).rev() { - let mut digit = 0; - let src = self - .0 - .get(digit_index * BITS_PER_DIGIT..) - .unwrap_or_default(); - let src = src.get(..BITS_PER_DIGIT).unwrap_or(src); - digit.view_bits_mut::()[..src.len()].copy_from_bitslice(src); - let mut ch = char::from_digit(digit as u32, 1 << BITS_PER_DIGIT).unwrap(); - if UPPER_CASE { - ch = ch.to_ascii_uppercase(); - } - buf.push(ch); - } - f.pad_integral( - true, - match BITS_PER_DIGIT { - 1 => "0b", - 3 => "0o", - 4 => "0x", - _ => "", - }, - &buf, - ) - } -} - -impl fmt::Binary for BitSliceWriteWithBase<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.fmt_with_base::<1, false>(f) - } -} - -impl fmt::Octal for BitSliceWriteWithBase<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.fmt_with_base::<3, false>(f) - } -} - -impl fmt::LowerHex for BitSliceWriteWithBase<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.fmt_with_base::<4, false>(f) - } -} - -impl fmt::UpperHex for BitSliceWriteWithBase<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.fmt_with_base::<4, true>(f) - } -} +#[doc(inline)] +pub use misc::{ + interned_bit, iter_eq_by, BitSliceWriteWithBase, DebugAsDisplay, DebugAsRawString, MakeMutSlice, +}; diff --git a/crates/fayalite/src/util/const_bool.rs b/crates/fayalite/src/util/const_bool.rs new file mode 100644 index 0000000..7033d6a --- /dev/null +++ b/crates/fayalite/src/util/const_bool.rs @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information +use std::{fmt::Debug, hash::Hash, mem::ManuallyDrop, ptr}; + +mod sealed { + pub trait Sealed {} +} + +/// # Safety +/// the only implementation is `ConstBool` +pub unsafe trait GenericConstBool: + sealed::Sealed + Copy + Ord + Hash + Default + Debug + 'static + Send + Sync +{ + const VALUE: bool; +} + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] +pub struct ConstBool; + +impl Debug for ConstBool { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("ConstBool").field(&Self::VALUE).finish() + } +} + +impl sealed::Sealed for ConstBool {} + +/// SAFETY: the only implementation is `ConstBool` +unsafe impl GenericConstBool for ConstBool { + const VALUE: bool = VALUE; +} + +pub trait ConstBoolDispatchTag { + type Type; +} + +pub enum ConstBoolDispatch { + False(FalseT), + True(TrueT), +} + +impl ConstBoolDispatch { + pub fn new< + Tag: ConstBoolDispatchTag> = FalseT> + + ConstBoolDispatchTag> = TrueT>, + Select: GenericConstBool, + >( + v: Tag::Type = &*v; + // SAFETY: reads the exact same type, since Select is really ConstBool + unsafe { + if Select::VALUE { + ConstBoolDispatch::True(ptr::read(v_ptr.cast())) + } else { + ConstBoolDispatch::False(ptr::read(v_ptr.cast())) + } + } + } +} diff --git a/crates/fayalite/src/util/const_cmp.rs b/crates/fayalite/src/util/const_cmp.rs new file mode 100644 index 0000000..76530ed --- /dev/null +++ b/crates/fayalite/src/util/const_cmp.rs @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information +use std::cmp::Ordering; + +pub const fn const_u8_cmp(a: u8, b: u8) -> Ordering { + if a < b { + Ordering::Less + } else if a > b { + Ordering::Greater + } else { + Ordering::Equal + } +} + +pub const fn const_usize_cmp(a: usize, b: usize) -> Ordering { + if a < b { + Ordering::Less + } else if a > b { + Ordering::Greater + } else { + Ordering::Equal + } +} + +pub const fn const_bytes_cmp(a: &[u8], b: &[u8]) -> Ordering { + let mut i = 0; + while i < a.len() && i < b.len() { + match const_u8_cmp(a[i], b[i]) { + Ordering::Equal => {} + retval => return retval, + } + i += 1; + } + const_usize_cmp(a.len(), b.len()) +} + +pub const fn const_str_cmp(a: &str, b: &str) -> Ordering { + const_bytes_cmp(a.as_bytes(), b.as_bytes()) +} + +pub const fn const_str_array_is_strictly_ascending(a: &[&str]) -> bool { + let mut i = 1; + while i < a.len() { + match const_str_cmp(a[i - 1], a[i]) { + Ordering::Less => {} + _ => return false, + } + i += 1; + } + true +} diff --git a/crates/fayalite/src/util/misc.rs b/crates/fayalite/src/util/misc.rs new file mode 100644 index 0000000..884458f --- /dev/null +++ b/crates/fayalite/src/util/misc.rs @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information +use crate::intern::{Intern, Interned}; +use bitvec::{bits, order::Lsb0, slice::BitSlice, view::BitView}; +use std::{ + fmt::{self, Debug, Write}, + rc::Rc, + sync::{Arc, OnceLock}, +}; + +/// replacement for Iterator::eq_by which isn't stable yet +pub fn iter_eq_by bool>( + l: L, + r: R, + mut f: F, +) -> bool { + let mut l = l.into_iter(); + let mut r = r.into_iter(); + l.try_for_each(|l| { + if let Some(r) = r.next() { + f(l, r).then_some(()) + } else { + None + } + }) + .is_some() + && r.next().is_none() +} + +pub struct DebugAsDisplay(pub T); + +impl fmt::Debug for DebugAsDisplay { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +pub trait MakeMutSlice { + type Element: Clone; + fn make_mut_slice(&mut self) -> &mut [Self::Element]; +} + +impl MakeMutSlice for Arc<[T]> { + type Element = T; + + fn make_mut_slice(&mut self) -> &mut [Self::Element] { + if Arc::get_mut(self).is_none() { + *self = Arc::<[T]>::from(&**self); + } + Arc::get_mut(self).unwrap() + } +} + +impl MakeMutSlice for Rc<[T]> { + type Element = T; + + fn make_mut_slice(&mut self) -> &mut [Self::Element] { + if Rc::get_mut(self).is_none() { + *self = Rc::<[T]>::from(&**self); + } + Rc::get_mut(self).unwrap() + } +} + +pub struct DebugAsRawString<'a>(pub &'a str); + +impl fmt::Debug for DebugAsRawString<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let pounds = self + .0 + .split_inclusive('"') + .skip(1) + .map(|v| v.len() - v.trim_start_matches('#').len()) + .max() + .map(|v| v + 1) + .unwrap_or(0); + f.write_char('r')?; + for _ in 0..pounds { + f.write_char('#')?; + } + f.write_char('"')?; + f.write_str(self.0)?; + f.write_char('"')?; + for _ in 0..pounds { + f.write_char('#')?; + } + Ok(()) + } +} + +pub fn interned_bit(v: bool) -> Interned { + static RETVAL: OnceLock<[Interned; 2]> = OnceLock::new(); + // use bits![V; 1] instead of bits![V] to work around https://github.com/ferrilab/bitvec/issues/271 + RETVAL.get_or_init(|| [bits![0; 1].intern(), bits![1; 1].intern()])[v as usize] +} + +#[derive(Copy, Clone, Debug)] +pub struct BitSliceWriteWithBase<'a>(pub &'a BitSlice); + +impl BitSliceWriteWithBase<'_> { + fn fmt_with_base( + self, + f: &mut fmt::Formatter<'_>, + ) -> fmt::Result { + let digit_count = self.0.len().div_ceil(BITS_PER_DIGIT).max(1); + // TODO: optimize + let mut buf = String::with_capacity(digit_count); + for digit_index in (0..digit_count).rev() { + let mut digit = 0; + let src = self + .0 + .get(digit_index * BITS_PER_DIGIT..) + .unwrap_or_default(); + let src = src.get(..BITS_PER_DIGIT).unwrap_or(src); + digit.view_bits_mut::()[..src.len()].copy_from_bitslice(src); + let mut ch = char::from_digit(digit as u32, 1 << BITS_PER_DIGIT).unwrap(); + if UPPER_CASE { + ch = ch.to_ascii_uppercase(); + } + buf.push(ch); + } + f.pad_integral( + true, + match BITS_PER_DIGIT { + 1 => "0b", + 3 => "0o", + 4 => "0x", + _ => "", + }, + &buf, + ) + } +} + +impl fmt::Binary for BitSliceWriteWithBase<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.fmt_with_base::<1, false>(f) + } +} + +impl fmt::Octal for BitSliceWriteWithBase<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.fmt_with_base::<3, false>(f) + } +} + +impl fmt::LowerHex for BitSliceWriteWithBase<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.fmt_with_base::<4, false>(f) + } +} + +impl fmt::UpperHex for BitSliceWriteWithBase<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.fmt_with_base::<4, true>(f) + } +}