forked from Orchid/orchid
in midst of refactor
This commit is contained in:
23
orchidlang/src/utils/boxed_iter.rs
Normal file
23
orchidlang/src/utils/boxed_iter.rs
Normal 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;
|
||||
82
orchidlang/src/utils/clonable_iter.rs
Normal file
82
orchidlang/src/utils/clonable_iter.rs
Normal 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()) }
|
||||
}
|
||||
24
orchidlang/src/utils/combine.rs
Normal file
24
orchidlang/src/utils/combine.rs
Normal 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(()) }
|
||||
}
|
||||
39
orchidlang/src/utils/ddispatch.rs
Normal file
39
orchidlang/src/utils/ddispatch.rs
Normal 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
|
||||
}
|
||||
13
orchidlang/src/utils/get_or.rs
Normal file
13
orchidlang/src/utils/get_or.rs
Normal 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
|
||||
}
|
||||
44
orchidlang/src/utils/iter_find.rs
Normal file
44
orchidlang/src/utils/iter_find.rs
Normal 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
|
||||
}
|
||||
34
orchidlang/src/utils/join.rs
Normal file
34
orchidlang/src/utils/join.rs
Normal 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)
|
||||
}
|
||||
19
orchidlang/src/utils/mod.rs
Normal file
19
orchidlang/src/utils/mod.rs
Normal 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;
|
||||
35
orchidlang/src/utils/pure_seq.rs
Normal file
35
orchidlang/src/utils/pure_seq.rs
Normal 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)
|
||||
}
|
||||
27
orchidlang/src/utils/sequence.rs
Normal file
27
orchidlang/src/utils/sequence.rs
Normal 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()) }
|
||||
}
|
||||
97
orchidlang/src/utils/side.rs
Normal file
97
orchidlang/src/utils/side.rs
Normal 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")
|
||||
}
|
||||
}
|
||||
20
orchidlang/src/utils/string_from_charset.rs
Normal file
20
orchidlang/src/utils/string_from_charset.rs
Normal 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)
|
||||
}
|
||||
11
orchidlang/src/utils/take_with_output.rs
Normal file
11
orchidlang/src/utils/take_with_output.rs
Normal 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
|
||||
})
|
||||
}
|
||||
27
orchidlang/src/utils/unwrap_or.rs
Normal file
27
orchidlang/src/utils/unwrap_or.rs
Normal 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;
|
||||
Reference in New Issue
Block a user