in midst of refactor

This commit is contained in:
2024-04-29 21:46:42 +02:00
parent ed0d64d52e
commit aa3f7e99ab
221 changed files with 5431 additions and 685 deletions

View File

@@ -0,0 +1,23 @@
/// Utility functions to get rid of tedious explicit casts to
/// BoxedIter
use std::iter;
/// A trait object of [Iterator] to be assigned to variables that may be
/// initialized form multiple iterators of incompatible types
pub type BoxedIter<'a, T> = Box<dyn Iterator<Item = T> + 'a>;
/// creates a [BoxedIter] of a single element
pub fn box_once<'a, T: 'a>(t: T) -> BoxedIter<'a, T> { Box::new(iter::once(t)) }
/// creates an empty [BoxedIter]
pub fn box_empty<'a, T: 'a>() -> BoxedIter<'a, T> { Box::new(iter::empty()) }
/// Chain various iterators into a [BoxedIter]
macro_rules! box_chain {
($curr:expr) => {
Box::new($curr) as BoxedIter<_>
};
($curr:expr, $($rest:expr),*) => {
Box::new($curr$(.chain($rest))*) as $crate::utils::boxed_iter::BoxedIter<_>
};
}
pub(crate) use box_chain;

View File

@@ -0,0 +1,82 @@
use std::sync::{Arc, Mutex};
use crate::utils::take_with_output::take_with_output;
enum State<I: Iterator> {
End,
Head(I),
Cont(Clonable<I>, I::Item),
}
/// Wraps a regular iterator and buffers previously emitted elements, to ensure
/// that clones of this iterator emit the same sequence of elements
/// independently of each other. Note that this ruins pretty much all of Rust's
/// iterator-related optimizations and allocates each buffered element on the
/// heap.
pub struct Clonable<I: Iterator>(Arc<Mutex<State<I>>>);
impl<I> Clonable<I>
where
I: Iterator,
I::Item: Clone,
{
pub fn new(iter: impl IntoIterator<IntoIter = I, Item = I::Item>) -> Self {
Self::wrap(State::Head(iter.into_iter()))
}
fn wrap(s: State<I>) -> Self { Self(Arc::new(Mutex::new(s))) }
}
impl<I> Iterator for Clonable<I>
where
I: Iterator,
I::Item: Clone,
{
type Item = I::Item;
fn next(&mut self) -> Option<Self::Item> {
take_with_output(self, |Self(arc)| match Arc::try_unwrap(arc) {
Ok(mutex) => match mutex.into_inner().unwrap() {
State::End => (Self::wrap(State::End), None),
State::Cont(next, data) => (next, Some(data)),
State::Head(mut iter) => match iter.next() {
None => (Self::wrap(State::End), None),
Some(data) => (Self::wrap(State::Head(iter)), Some(data)),
},
},
Err(arc) => take_with_output(&mut *arc.lock().unwrap(), |s| match s {
State::End => (State::End, (Self::wrap(State::End), None)),
State::Cont(next, data) => (State::Cont(next.clone(), data.clone()), (next, Some(data))),
State::Head(mut iter) => match iter.next() {
None => (State::End, (Self::wrap(State::End), None)),
Some(data) => {
let head = Self::wrap(State::Head(iter));
(State::Cont(head.clone(), data.clone()), (head, Some(data)))
},
},
}),
})
}
fn size_hint(&self) -> (usize, Option<usize>) {
let mut steps = 0;
let mut cur = self.0.clone();
loop {
let guard = cur.lock().unwrap();
match &*guard {
State::End => break (steps, Some(steps)),
State::Head(i) => {
let (min, max) = i.size_hint();
break (min + steps, max.map(|s| s + steps));
},
State::Cont(next, _) => {
let tmp = next.0.clone();
drop(guard);
cur = tmp;
steps += 1;
},
}
}
}
}
impl<I: Iterator> Clone for Clonable<I> {
fn clone(&self) -> Self { Self(self.0.clone()) }
}

View File

@@ -0,0 +1,24 @@
//! The concept of a fallible merger
use never::Never;
/// Fallible, type-preserving variant of [std::ops::Add] implemented by a
/// variety of types for different purposes. Very broadly, if the operation
/// succeeds, the result should represent _both_ inputs.
pub trait Combine: Sized {
/// Information about the failure
type Error;
/// Merge two values into a value that represents both, if this is possible.
fn combine(self, other: Self) -> Result<Self, Self::Error>;
}
impl Combine for Never {
type Error = Never;
fn combine(self, _: Self) -> Result<Self, Self::Error> { match self {} }
}
impl Combine for () {
type Error = Never;
fn combine(self, (): Self) -> Result<Self, Self::Error> { Ok(()) }
}

View File

@@ -0,0 +1,39 @@
//! A simplified, stable variant of `std::any::Provider`.
use std::any::Any;
/// A request for a value of an unknown type
pub struct Request<'a>(&'a mut dyn Any);
impl<'a> Request<'a> {
/// Checks if a value of the given type would serve the request, and the
/// request had not yet been served
pub fn can_serve<T: 'static>(&self) -> bool {
self.0.downcast_ref::<Option<T>>().map_or(false, Option::is_none)
}
/// Serve a value if it's the correct type
pub fn serve<T: 'static>(&mut self, value: T) { self.serve_with::<T>(|| value) }
/// Invoke the callback to serve the request only if the return type matches
pub fn serve_with<T: 'static>(&mut self, provider: impl FnOnce() -> T) {
if let Some(slot) = self.0.downcast_mut::<Option<T>>() {
if slot.is_none() {
*slot = Some(provider());
}
}
}
}
/// Trait for objects that can respond to type-erased commands. This trait is
/// a dependency of `Atomic` but the implementation can be left empty.
pub trait Responder {
/// Try to provide as many types as we support
fn respond(&self, _request: Request) {}
}
/// Request a specific contract type from a responder
pub fn request<T: 'static>(responder: &(impl Responder + ?Sized)) -> Option<T> {
let mut slot = None;
responder.respond(Request(&mut slot));
slot
}

View File

@@ -0,0 +1,13 @@
use std::hash::Hash;
use hashbrown::HashMap;
/// Get the given value from the map or initialize it with the callback if it
/// doesn't exist, then return a mutable reference.
pub fn get_or_make<'a, K: Eq + Hash + Clone, V>(
map: &'a mut HashMap<K, V>,
k: &K,
make: impl FnOnce() -> V,
) -> &'a mut V {
map.raw_entry_mut().from_key(k).or_insert_with(|| (k.clone(), make())).1
}

View File

@@ -0,0 +1,44 @@
/// Check if the finite sequence produced by a clonable iterator (`haystack`)
/// contains the finite sequence produced by another clonable iterator
/// (`needle`)
pub fn iter_find<T: Eq>(
mut haystack: impl Iterator<Item = T> + Clone,
needle: impl Iterator<Item = T> + Clone,
) -> Option<usize> {
let mut start = 0;
loop {
match iter_starts_with(haystack.clone(), needle.clone()) {
ISWResult::StartsWith => return Some(start),
ISWResult::Shorter => return None,
ISWResult::Difference => (),
}
haystack.next();
start += 1;
}
}
/// Value returned by iter_starts_with
enum ISWResult {
/// The first iterator starts with the second
StartsWith,
/// The values of the two iterators differ
Difference,
/// The first iterator ends before the second
Shorter,
}
/// Checks that an iterator starts with another
fn iter_starts_with<T: Eq>(
mut a: impl Iterator<Item = T>,
b: impl Iterator<Item = T>,
) -> ISWResult {
// if a starts with b then for every element in b
for item in b {
match a.next() {
Some(comp) if item == comp => (),
Some(_) => return ISWResult::Difference,
None => return ISWResult::Shorter,
}
}
ISWResult::StartsWith
}

View File

@@ -0,0 +1,34 @@
//! Join hashmaps with a callback for merging or failing on conflicting keys.
use std::hash::Hash;
use hashbrown::HashMap;
use never::Never;
/// Combine two hashmaps via an infallible value merger. See also
/// [try_join_maps]
pub fn join_maps<K: Eq + Hash, V>(
left: HashMap<K, V>,
right: HashMap<K, V>,
mut merge: impl FnMut(&K, V, V) -> V,
) -> HashMap<K, V> {
try_join_maps(left, right, |k, l, r| Ok(merge(k, l, r))).unwrap_or_else(|e: Never| match e {})
}
/// Combine two hashmaps via a fallible value merger. See also [join_maps]
pub fn try_join_maps<K: Eq + Hash, V, E>(
left: HashMap<K, V>,
mut right: HashMap<K, V>,
mut merge: impl FnMut(&K, V, V) -> Result<V, E>,
) -> Result<HashMap<K, V>, E> {
let mut mixed = HashMap::with_capacity(left.len() + right.len());
for (key, lval) in left {
let val = match right.remove(&key) {
None => lval,
Some(rval) => merge(&key, lval, rval)?,
};
mixed.insert(key, val);
}
mixed.extend(right);
Ok(mixed)
}

View File

@@ -0,0 +1,19 @@
//! Utilities that don't necessarily have a well-defined role in the
//! problem-domain of Orchid but are rather designed to fulfill abstract
//! project-domain tasks.
//!
//! An unreferenced util should be either moved out to a package or deleted
pub(crate) mod boxed_iter;
pub(crate) mod clonable_iter;
pub mod combine;
pub mod ddispatch;
pub(crate) mod get_or;
pub(crate) mod iter_find;
pub mod join;
pub mod pure_seq;
pub mod sequence;
pub mod side;
pub mod string_from_charset;
pub mod take_with_output;
pub(crate) mod unwrap_or;

View File

@@ -0,0 +1,35 @@
//! Methods to operate on Rust vectors in a declarative manner
use std::iter;
/// Pure version of [Vec::push]
///
/// Create a new vector consisting of the provided vector with the
/// element appended. See [pushed_ref] to use it with a slice
pub fn pushed<I: IntoIterator, C: FromIterator<I::Item>>(vec: I, t: I::Item) -> C {
vec.into_iter().chain(iter::once(t)).collect()
}
/// Pure version of [Vec::push]
///
/// Create a new vector consisting of the provided slice with the
/// element appended. See [pushed] for the owned version
pub fn pushed_ref<'a, T: Clone + 'a, C: FromIterator<T>>(
vec: impl IntoIterator<Item = &'a T>,
t: T,
) -> C {
vec.into_iter().cloned().chain(iter::once(t)).collect()
}
/// Push an element on the adhoc stack, pass it to the callback, then pop the
/// element out again.
pub fn with_pushed<T, U>(
vec: &mut Vec<T>,
item: T,
cb: impl for<'a> FnOnce(&'a mut Vec<T>) -> U,
) -> (T, U) {
vec.push(item);
let out = cb(vec);
let item = vec.pop().expect("top element stolen by callback");
(item, out)
}

View File

@@ -0,0 +1,27 @@
//! An alternative to `Iterable` in many languages, a [Fn] that returns an
//! iterator.
use std::rc::Rc;
use trait_set::trait_set;
use super::boxed_iter::BoxedIter;
trait_set! {
trait Payload<'a, T> = Fn() -> BoxedIter<'a, T> + 'a;
}
/// Dynamic iterator building callback. Given how many trait objects this
/// involves, it may actually be slower than C#.
pub struct Sequence<'a, T: 'a>(Rc<dyn Payload<'a, T>>);
impl<'a, T: 'a> Sequence<'a, T> {
/// Construct from a concrete function returning a concrete iterator
pub fn new<I: IntoIterator<Item = T> + 'a>(f: impl Fn() -> I + 'a) -> Self {
Self(Rc::new(move || Box::new(f().into_iter())))
}
/// Get an iterator from the function
pub fn iter(&self) -> impl Iterator<Item = T> + '_ { (self.0)() }
}
impl<'a, T: 'a> Clone for Sequence<'a, T> {
fn clone(&self) -> Self { Self(self.0.clone()) }
}

View File

@@ -0,0 +1,97 @@
//! Named left/right. I tried bools, I couldn't consistently remember which one
//! is left, so I made an enum. Rust should optimize this into a bool anyway.
use std::fmt;
use std::ops::Not;
use super::boxed_iter::BoxedIter;
/// A primitive for encoding the two sides Left and Right. While booleans
/// are technically usable for this purpose, they're very easy to confuse
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Side {
/// Left, low, or high-to-low in the case of sequences
Left,
/// Right, high, or low-to-high in the case of sequences
Right,
}
impl fmt::Display for Side {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Left => write!(f, "Left"),
Self::Right => write!(f, "Right"),
}
}
}
impl Side {
/// Get the side that is not the current one
pub fn opposite(&self) -> Self {
match self {
Self::Left => Self::Right,
Self::Right => Self::Left,
}
}
/// Shorthand for opposite
pub fn inv(&self) -> Self { self.opposite() }
/// take N elements from this end of a slice
pub fn slice<'a, T>(&self, size: usize, slice: &'a [T]) -> &'a [T] {
match self {
Side::Left => &slice[..size],
Side::Right => &slice[slice.len() - size..],
}
}
/// ignore N elements from this end of a slice
pub fn crop<'a, T>(&self, margin: usize, slice: &'a [T]) -> &'a [T] {
self.opposite().slice(slice.len() - margin, slice)
}
/// ignore N elements from this end and M elements from the other end
/// of a slice
pub fn crop_both<'a, T>(&self, margin: usize, opposite: usize, slice: &'a [T]) -> &'a [T] {
self.crop(margin, self.opposite().crop(opposite, slice))
}
/// Pick this side from a pair of things
pub fn pick<T>(&self, pair: (T, T)) -> T {
match self {
Side::Left => pair.0,
Side::Right => pair.1,
}
}
/// Make a pair with the first element on this side
pub fn pair<T>(&self, this: T, opposite: T) -> (T, T) {
match self {
Side::Left => (this, opposite),
Side::Right => (opposite, this),
}
}
/// Walk a double ended iterator (assumed to be left-to-right) in this
/// direction
pub fn walk<'a, I: DoubleEndedIterator + 'a>(&self, iter: I) -> BoxedIter<'a, I::Item> {
match self {
Side::Right => Box::new(iter) as BoxedIter<I::Item>,
Side::Left => Box::new(iter.rev()),
}
}
}
impl Not for Side {
type Output = Side;
fn not(self) -> Self::Output { self.opposite() }
}
#[cfg(test)]
mod test {
use itertools::Itertools;
use super::*;
/// I apparently have a tendency to mix these up so it's best if
/// the sides are explicitly stated
#[test]
fn test_walk() {
assert_eq!(Side::Right.walk(0..4).collect_vec(), vec![0, 1, 2, 3], "can walk a range");
assert_eq!(Side::Left.walk(0..4).collect_vec(), vec![3, 2, 1, 0], "can walk a range backwards")
}
}

View File

@@ -0,0 +1,20 @@
//! Generate valid names from numbers and a character set. For small numbers,
//! the results should be substantially more memorable than the plain numbers.
fn string_from_charset_rec(val: u64, digits: &str) -> String {
let radix = digits.len() as u64;
let mut prefix =
if val > radix { string_from_charset_rec(val / radix, digits) } else { String::new() };
let digit = digits
.chars()
.nth(val as usize - 1)
.unwrap_or_else(|| panic!("Overindexed digit set \"{}\" with {}", digits, val - 1));
prefix.push(digit);
prefix
}
/// Generate alphabetized names from numbers using a set of permitted
/// characters. Especially practical in combination with De Bruijn indices
pub fn string_from_charset(val: u64, digits: &str) -> String {
string_from_charset_rec(val + 1, digits)
}

View File

@@ -0,0 +1,11 @@
//! Map over a `&mut` and return an additional value
/// A variation on [take_mut::take] that allows the callback to return a value
pub fn take_with_output<T, U>(src: &mut T, cb: impl FnOnce(T) -> (T, U)) -> U {
take_mut::scoped::scope(|scope| {
let (old, hole) = scope.take(src);
let (new, out) = cb(old);
hole.fill(new);
out
})
}

View File

@@ -0,0 +1,27 @@
/// A macro version of [Option::unwrap_or_else] which supports flow
/// control statements such as `return` and `break` in the "else" branch.
///
/// ```ignore
/// crate::unwrap_or!(Some(1); return)
/// ```
///
/// It also supports unwrapping concrete variants of other enums
///
/// ```ignore
/// use crate::Literal;
///
/// crate::unwrap_or!(Literal::Usize(2) => Literal::Number; return)
/// ```
///
/// Note: this macro influences the control flow of the surrounding code
/// without an `if`, which can be misleading. It should only be used for small,
/// straightforward jumps.
macro_rules! unwrap_or {
($m:expr; $fail:expr) => {{ if let Some(res) = ($m) { res } else { $fail } }};
($m:expr => $pattern:path; $fail:expr) => {
// rustfmt keeps inlining this and then complaining about its length
{ if let $pattern(res) = ($m) { res } else { $fail } }
};
}
pub(crate) use unwrap_or;