This commit is contained in:
parent
14a9c23697
commit
be025c14ca
|
@ -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<Self::VALUE>`
|
||||
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<const VALUE: bool>;
|
||||
|
||||
impl<const VALUE: bool> Debug for ConstBool<VALUE> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_tuple("ConstBool").field(&Self::VALUE).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<const VALUE: bool> sealed::Sealed for ConstBool<VALUE> {}
|
||||
|
||||
/// SAFETY: the only implementation is `ConstBool<Self::VALUE>`
|
||||
unsafe impl<const VALUE: bool> GenericConstBool for ConstBool<VALUE> {
|
||||
const VALUE: bool = VALUE;
|
||||
}
|
||||
|
||||
pub trait ConstBoolDispatchTag {
|
||||
type Type<Select: GenericConstBool>;
|
||||
}
|
||||
|
||||
pub enum ConstBoolDispatch<FalseT, TrueT> {
|
||||
False(FalseT),
|
||||
True(TrueT),
|
||||
}
|
||||
|
||||
impl<FalseT, TrueT> ConstBoolDispatch<FalseT, TrueT> {
|
||||
pub fn new<
|
||||
Tag: ConstBoolDispatchTag<Type<ConstBool<false>> = FalseT>
|
||||
+ ConstBoolDispatchTag<Type<ConstBool<true>> = TrueT>,
|
||||
Select: GenericConstBool,
|
||||
>(
|
||||
v: Tag::Type<Select>,
|
||||
) -> Self {
|
||||
let v = ManuallyDrop::new(v);
|
||||
let v_ptr: *const Tag::Type<Select> = &*v;
|
||||
// SAFETY: reads the exact same type, since Select is really ConstBool<Select::VALUE>
|
||||
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<L: IntoIterator, R: IntoIterator, F: FnMut(L::Item, R::Item) -> 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<T>(pub T);
|
||||
|
||||
impl<T: fmt::Display> fmt::Debug for DebugAsDisplay<T> {
|
||||
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<T: Clone> 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<T: Clone> 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<BitSlice> {
|
||||
static RETVAL: OnceLock<[Interned<BitSlice>; 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<const BITS_PER_DIGIT: usize, const UPPER_CASE: bool>(
|
||||
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::<Lsb0>()[..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,
|
||||
};
|
||||
|
|
61
crates/fayalite/src/util/const_bool.rs
Normal file
61
crates/fayalite/src/util/const_bool.rs
Normal file
|
@ -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<Self::VALUE>`
|
||||
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<const VALUE: bool>;
|
||||
|
||||
impl<const VALUE: bool> Debug for ConstBool<VALUE> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_tuple("ConstBool").field(&Self::VALUE).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<const VALUE: bool> sealed::Sealed for ConstBool<VALUE> {}
|
||||
|
||||
/// SAFETY: the only implementation is `ConstBool<Self::VALUE>`
|
||||
unsafe impl<const VALUE: bool> GenericConstBool for ConstBool<VALUE> {
|
||||
const VALUE: bool = VALUE;
|
||||
}
|
||||
|
||||
pub trait ConstBoolDispatchTag {
|
||||
type Type<Select: GenericConstBool>;
|
||||
}
|
||||
|
||||
pub enum ConstBoolDispatch<FalseT, TrueT> {
|
||||
False(FalseT),
|
||||
True(TrueT),
|
||||
}
|
||||
|
||||
impl<FalseT, TrueT> ConstBoolDispatch<FalseT, TrueT> {
|
||||
pub fn new<
|
||||
Tag: ConstBoolDispatchTag<Type<ConstBool<false>> = FalseT>
|
||||
+ ConstBoolDispatchTag<Type<ConstBool<true>> = TrueT>,
|
||||
Select: GenericConstBool,
|
||||
>(
|
||||
v: Tag::Type<Select>,
|
||||
) -> Self {
|
||||
let v = ManuallyDrop::new(v);
|
||||
let v_ptr: *const Tag::Type<Select> = &*v;
|
||||
// SAFETY: reads the exact same type, since Select is really ConstBool<Select::VALUE>
|
||||
unsafe {
|
||||
if Select::VALUE {
|
||||
ConstBoolDispatch::True(ptr::read(v_ptr.cast()))
|
||||
} else {
|
||||
ConstBoolDispatch::False(ptr::read(v_ptr.cast()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
51
crates/fayalite/src/util/const_cmp.rs
Normal file
51
crates/fayalite/src/util/const_cmp.rs
Normal file
|
@ -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
|
||||
}
|
157
crates/fayalite/src/util/misc.rs
Normal file
157
crates/fayalite/src/util/misc.rs
Normal file
|
@ -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<L: IntoIterator, R: IntoIterator, F: FnMut(L::Item, R::Item) -> 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<T>(pub T);
|
||||
|
||||
impl<T: fmt::Display> fmt::Debug for DebugAsDisplay<T> {
|
||||
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<T: Clone> 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<T: Clone> 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<BitSlice> {
|
||||
static RETVAL: OnceLock<[Interned<BitSlice>; 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<const BITS_PER_DIGIT: usize, const UPPER_CASE: bool>(
|
||||
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::<Lsb0>()[..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)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue