Preparation for sharing
- rustfmt - clippy - comments - README
This commit is contained in:
@@ -1,72 +1,41 @@
|
||||
use std::cell::RefCell;
|
||||
use std::hash::Hash;
|
||||
use std::rc::Rc;
|
||||
|
||||
use hashbrown::HashMap;
|
||||
|
||||
// TODO: make this a crate
|
||||
pub trait Callback<'a, I, O: 'static> =
|
||||
Fn(I, &Cache<'a, I, O>) -> O;
|
||||
pub trait Callback<'a, I, O: 'static> = Fn(I, &Cache<'a, I, O>) -> O;
|
||||
|
||||
pub type CbBox<'a, I, O> =
|
||||
Box<dyn Callback<'a, I, O> + 'a>;
|
||||
pub type CbBox<'a, I, O> = Box<dyn Callback<'a, I, O> + 'a>;
|
||||
|
||||
/// Cache the return values of an effectless closure in a hashmap
|
||||
/// Inspired by the closure_cacher crate.
|
||||
pub struct Cache<'a, I, O: 'static> {
|
||||
store: RefCell<HashMap<I, O>>,
|
||||
closure: CbBox<'a, I, O>
|
||||
closure: CbBox<'a, I, O>,
|
||||
}
|
||||
|
||||
impl<'a,
|
||||
I: Eq + Hash + Clone,
|
||||
O: Clone
|
||||
> Cache<'a, I, O> {
|
||||
impl<'a, I: Eq + Hash + Clone, O: Clone> Cache<'a, I, O> {
|
||||
pub fn new<F: 'a + Callback<'a, I, O>>(closure: F) -> Self {
|
||||
Self {
|
||||
store: RefCell::new(HashMap::new()),
|
||||
closure: Box::new(closure)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn rc<
|
||||
F: 'a + Callback<'a, I, O>
|
||||
>(closure: F) -> Rc<Self> {
|
||||
Rc::new(Self::new(closure))
|
||||
Self { store: RefCell::new(HashMap::new()), closure: Box::new(closure) }
|
||||
}
|
||||
|
||||
/// Produce and cache a result by cloning I if necessary
|
||||
pub fn find(&self, i: &I) -> O {
|
||||
let closure = &self.closure;
|
||||
if let Some(v) = self.store.borrow().get(i) {
|
||||
return v.clone()
|
||||
return v.clone();
|
||||
}
|
||||
// In the moment of invocation the refcell is on immutable
|
||||
// this is important for recursive calculations
|
||||
let result = closure(i.clone(), self);
|
||||
let mut store = self.store.borrow_mut();
|
||||
store.raw_entry_mut().from_key(i)
|
||||
.or_insert_with(|| (i.clone(), result)).1.clone()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
/// Return the result if it has already been computed
|
||||
pub fn known(&self, i: &I) -> Option<O> {
|
||||
let store = self.store.borrow();
|
||||
store.get(i).cloned()
|
||||
}
|
||||
|
||||
|
||||
/// Convert this cache into a cached [Fn(&I) -> O]
|
||||
#[allow(unused)]
|
||||
pub fn into_fn(self) -> impl Fn(&I) -> O + 'a where I: 'a {
|
||||
move |i| self.find(i)
|
||||
}
|
||||
|
||||
/// Borrow this cache with a cached [Fn(&I) -> O]
|
||||
#[allow(unused)]
|
||||
pub fn as_fn<'b: 'a>(&'b self) -> impl Fn(&I) -> O + 'b where I: 'b {
|
||||
move |i| self.find(i)
|
||||
store
|
||||
.raw_entry_mut()
|
||||
.from_key(i)
|
||||
.or_insert_with(|| (i.clone(), result))
|
||||
.1
|
||||
.clone()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,8 +43,8 @@ impl<'a, I, O> IntoIterator for Cache<'a, I, O> {
|
||||
type IntoIter = hashbrown::hash_map::IntoIter<I, O>;
|
||||
type Item = (I, O);
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
let Cache{ store, .. } = self;
|
||||
let Cache { store, .. } = self;
|
||||
let map = store.into_inner();
|
||||
map.into_iter()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
/// Utility functions to get rid of explicit casts to BoxedIter which are tedious
|
||||
|
||||
/// 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>;
|
||||
/// A [BoxedIter] of [BoxedIter].
|
||||
pub type BoxedIterIter<'a, T> = BoxedIter<'a, BoxedIter<'a, T>>;
|
||||
/// BoxedIter of a single element
|
||||
/// creates a [BoxedIter] of a single element
|
||||
pub fn box_once<'a, T: 'a>(t: T) -> BoxedIter<'a, T> {
|
||||
Box::new(iter::once(t))
|
||||
}
|
||||
/// BoxedIter of no elements
|
||||
/// 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_export]
|
||||
macro_rules! box_chain {
|
||||
($curr:expr) => {
|
||||
@@ -23,16 +27,22 @@ macro_rules! box_chain {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn box_flatten<'a,
|
||||
/// Flatten an iterator of iterators into a boxed iterator of the inner
|
||||
/// nested values
|
||||
pub fn box_flatten<
|
||||
'a,
|
||||
T: 'a,
|
||||
I: 'a + Iterator<Item = J>,
|
||||
J: 'a + Iterator<Item = T>
|
||||
>(i: I) -> BoxedIter<'a, T> {
|
||||
J: 'a + Iterator<Item = T>,
|
||||
>(
|
||||
i: I,
|
||||
) -> BoxedIter<'a, T> {
|
||||
Box::new(i.flatten())
|
||||
}
|
||||
|
||||
pub fn into_boxed_iter<'a,
|
||||
T: 'a + IntoIterator
|
||||
>(t: T) -> BoxedIter<'a, <T as IntoIterator>::Item> {
|
||||
/// Convert an iterator into a Box<dyn Iterator>
|
||||
pub fn into_boxed_iter<'a, T: 'a + IntoIterator>(
|
||||
t: T,
|
||||
) -> BoxedIter<'a, <T as IntoIterator>::Item> {
|
||||
Box::new(t.into_iter())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,19 @@
|
||||
mod cache;
|
||||
mod replace_first;
|
||||
// mod interned_display;
|
||||
// mod interner;
|
||||
mod variant;
|
||||
mod print_nname;
|
||||
mod pushed;
|
||||
pub use pushed::pushed;
|
||||
pub use print_nname::{print_nname_seq, print_nname};
|
||||
// pub use interner::*;
|
||||
// pub use interned_display::InternedDisplay;
|
||||
pub use replace_first::replace_first;
|
||||
pub use cache::Cache;
|
||||
mod substack;
|
||||
pub use substack::{Stackframe, Substack, SubstackIterator};
|
||||
mod replace_first;
|
||||
mod side;
|
||||
pub use side::Side;
|
||||
mod string_from_charset;
|
||||
mod substack;
|
||||
mod unwrap_or;
|
||||
mod xloop;
|
||||
|
||||
pub use cache::Cache;
|
||||
pub use print_nname::sym2string;
|
||||
pub use pushed::pushed;
|
||||
pub use replace_first::replace_first;
|
||||
pub use side::Side;
|
||||
pub use substack::{Stackframe, Substack, SubstackIterator};
|
||||
pub mod iter;
|
||||
pub use iter::BoxedIter;
|
||||
mod string_from_charset;
|
||||
pub use string_from_charset::string_from_charset;
|
||||
mod xloop;
|
||||
mod protomap;
|
||||
pub use protomap::ProtoMap;
|
||||
@@ -1,16 +1,8 @@
|
||||
use itertools::Itertools;
|
||||
|
||||
use crate::interner::{Interner, Token};
|
||||
use crate::interner::{Interner, Sym};
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn print_nname(t: Token<Vec<Token<String>>>, i: &Interner) -> String {
|
||||
/// Print symbols to :: delimited strings
|
||||
pub fn sym2string(t: Sym, i: &Interner) -> String {
|
||||
i.r(t).iter().map(|t| i.r(*t)).join("::")
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn print_nname_seq<'a>(
|
||||
tv: impl Iterator<Item = &'a Token<Vec<Token<String>>>>,
|
||||
i: &Interner
|
||||
) -> String {
|
||||
tv.map(|t| print_nname(*t, i)).join(", ")
|
||||
}
|
||||
@@ -1,172 +0,0 @@
|
||||
use std::{iter, ops::{Index, Add}, borrow::Borrow};
|
||||
|
||||
use smallvec::SmallVec;
|
||||
|
||||
// TODO: make this a crate alongside substack
|
||||
|
||||
/// Linked-array-list of key-value pairs.
|
||||
/// - Lookup and modification is O(n + cachemiss * n / m)
|
||||
/// - Can be extended by reference in O(m) < O(n)
|
||||
///
|
||||
/// The number of elements stored inline in a stackframe is 2 by default,
|
||||
/// which is enough for most recursive algorithms.
|
||||
/// - The cost of overruns is a heap allocation and subsequent
|
||||
/// heap indirections, plus wasted stack space which is likely wasted L1
|
||||
/// as well.
|
||||
/// - The cost of underruns is wasted stack space.
|
||||
pub struct ProtoMap<'a, K, V, const STACK_COUNT: usize = 2> {
|
||||
entries: SmallVec<[(K, Option<V>); STACK_COUNT]>,
|
||||
prototype: Option<&'a ProtoMap<'a, K, V, STACK_COUNT>>
|
||||
}
|
||||
|
||||
impl<'a, K, V, const STACK_COUNT: usize> ProtoMap<'a, K, V, STACK_COUNT> {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
entries: SmallVec::new(),
|
||||
prototype: None
|
||||
}
|
||||
}
|
||||
|
||||
/// Mutable reference to entry without checking proto in O(m)
|
||||
fn local_entry_mut<'b, Q: ?Sized + Eq>(&'b mut self, query: &Q)
|
||||
-> Option<(usize, &'b mut K, &'b mut Option<V>)>
|
||||
where K: Borrow<Q>
|
||||
{
|
||||
self.entries.iter_mut().enumerate().find_map(|(i, (k, v))| {
|
||||
if query.eq((*k).borrow()) { Some((i, k, v)) } else { None }
|
||||
})
|
||||
}
|
||||
|
||||
/// Entry without checking proto in O(m)
|
||||
fn local_entry<'b, Q: ?Sized + Eq>(&'b self, query: &Q)
|
||||
-> Option<(usize, &'b K, &'b Option<V>)>
|
||||
where K: Borrow<Q>
|
||||
{
|
||||
self.entries.iter().enumerate().find_map(|(i, (k, v))| {
|
||||
if query.eq((*k).borrow()) { Some((i, k, v)) } else { None }
|
||||
})
|
||||
}
|
||||
|
||||
/// Find entry in prototype chain in O(n)
|
||||
pub fn get<'b, Q: ?Sized + Eq>(&'b self, query: &Q) -> Option<&'b V>
|
||||
where K: Borrow<Q>
|
||||
{
|
||||
if let Some((_, _, v)) = self.local_entry(query) {
|
||||
v.as_ref()
|
||||
} else {
|
||||
self.prototype?.get(query)
|
||||
}
|
||||
}
|
||||
|
||||
/// Record a value for the given key in O(m)
|
||||
pub fn set(&mut self, key: &K, value: V) where K: Eq + Clone {
|
||||
if let Some((_, _, v)) = self.local_entry_mut(key) {
|
||||
*v = Some(value);
|
||||
} else {
|
||||
self.entries.push((key.clone(), Some(value)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete in a memory-efficient way in O(n)
|
||||
pub fn delete_small(&mut self, key: &K) where K: Eq + Clone {
|
||||
let exists_up = self.prototype.and_then(|p| p.get(key)).is_some();
|
||||
let local_entry = self.local_entry_mut(key);
|
||||
match (exists_up, local_entry) {
|
||||
(false, None) => (), // nothing to do
|
||||
(false, Some((i, _, _))) => { self.entries.remove(i); }, // forget locally
|
||||
(true, Some((_, _, v))) => *v = None, // update local override to cover
|
||||
(true, None) => self.entries.push((key.clone(), None)), // create new
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete in O(m) without checking the prototype chain
|
||||
/// May produce unnecessary cover over previously unknown key
|
||||
pub fn delete_fast(&mut self, key: &K) where K: Eq + Clone {
|
||||
if let Some((_, _, v)) = self.local_entry_mut(key) {
|
||||
*v = None
|
||||
} else {
|
||||
self.entries.push((key.clone(), None))
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterate over the values defined herein and on the prototype chain
|
||||
/// Note that this will visit keys multiple times
|
||||
pub fn iter(&self) -> impl Iterator<Item = &(K, Option<V>)> {
|
||||
let mut map = self;
|
||||
iter::from_fn(move || {
|
||||
let pairs = map.entries.iter();
|
||||
map = map.prototype?;
|
||||
Some(pairs)
|
||||
}).flatten()
|
||||
}
|
||||
|
||||
/// Visit the keys in an unsafe random order, repeated arbitrarily many times
|
||||
pub fn keys(&self) -> impl Iterator<Item = &K> {
|
||||
self.iter().map(|(k, _)| k)
|
||||
}
|
||||
|
||||
/// Visit the values in random order
|
||||
pub fn values(&self) -> impl Iterator<Item = &V> {
|
||||
self.iter().filter_map(|(_, v)| v.as_ref())
|
||||
}
|
||||
|
||||
/// Update the prototype, and correspondingly the lifetime of the map
|
||||
pub fn set_proto<'b>(self, proto: &'b ProtoMap<'b, K, V, STACK_COUNT>)
|
||||
-> ProtoMap<'b, K, V, STACK_COUNT> {
|
||||
ProtoMap {
|
||||
entries: self.entries,
|
||||
prototype: Some(proto)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
K, V,
|
||||
T: IntoIterator<Item = (K, V)>,
|
||||
const STACK_COUNT: usize
|
||||
> From<T>
|
||||
for ProtoMap<'_, K, V, STACK_COUNT> {
|
||||
fn from(value: T) -> Self {
|
||||
Self {
|
||||
entries: value.into_iter().map(|(k, v)| (k, Some(v))).collect(),
|
||||
prototype: None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Q: ?Sized + Eq, K: Borrow<Q>, V, const STACK_COUNT: usize> Index<&Q>
|
||||
for ProtoMap<'_, K, V, STACK_COUNT> {
|
||||
type Output = V;
|
||||
fn index(&self, index: &Q) -> &Self::Output {
|
||||
self.get(index).expect("Index not found in map")
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: Clone, V: Clone, const STACK_COUNT: usize>
|
||||
Clone for ProtoMap<'_, K, V, STACK_COUNT> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
entries: self.entries.clone(),
|
||||
prototype: self.prototype
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, K: 'a, V: 'a, const STACK_COUNT: usize>
|
||||
Add<(K, V)> for &'a ProtoMap<'a, K, V, STACK_COUNT> {
|
||||
type Output = ProtoMap<'a, K, V, STACK_COUNT>;
|
||||
fn add(self, rhs: (K, V)) -> Self::Output {
|
||||
ProtoMap::from([rhs]).set_proto(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, K, V, const STACK_COUNT: usize> Default for ProtoMap<'a, K, V, STACK_COUNT> {
|
||||
fn default() -> Self { Self::new() }
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! protomap {
|
||||
($($ent:expr),*) => {
|
||||
ProtoMap::from([$($ent:expr),*])
|
||||
};
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
/// Pure version of [Vec::push]
|
||||
///
|
||||
/// Create a new vector consisting of the provided vector with the
|
||||
/// element appended
|
||||
pub fn pushed<T: Clone>(vec: &Vec<T>, t: T) -> Vec<T> {
|
||||
@@ -5,4 +7,4 @@ pub fn pushed<T: Clone>(vec: &Vec<T>, t: T) -> Vec<T> {
|
||||
next.extend_from_slice(&vec[..]);
|
||||
next.push(t);
|
||||
next
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
use std::iter;
|
||||
|
||||
/// Iterate over a sequence with the first element the function returns
|
||||
/// Some() for updated, but only if there is such an element.
|
||||
pub fn replace_first<
|
||||
T: Clone,
|
||||
F: FnMut(&T) -> Option<T>
|
||||
>(
|
||||
slice: &[T], mut f: F
|
||||
/// Iterate over a sequence with the first element updated for which the
|
||||
/// function returns Some(), but only if there is such an element.
|
||||
pub fn replace_first<T: Clone, F: FnMut(&T) -> Option<T>>(
|
||||
slice: &[T],
|
||||
mut f: F,
|
||||
) -> Option<impl Iterator<Item = T> + '_> {
|
||||
for i in 0..slice.len() {
|
||||
if let Some(new) = f(&slice[i]) {
|
||||
let subbed_iter = slice[0..i].iter().cloned()
|
||||
let subbed_iter = slice[0..i]
|
||||
.iter()
|
||||
.cloned()
|
||||
.chain(iter::once(new))
|
||||
.chain(slice[i+1..].iter().cloned());
|
||||
return Some(subbed_iter)
|
||||
.chain(slice[i + 1..].iter().cloned());
|
||||
return Some(subbed_iter);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
use std::fmt::Display;
|
||||
use std::ops::Not;
|
||||
|
||||
use super::BoxedIter;
|
||||
|
||||
/// A primitive for encoding the two sides Left and Right. While booleans
|
||||
/// are technically usable for this purpose, they're less descriptive.
|
||||
/// are technically usable for this purpose, they're very easy to confuse
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum Side {Left, Right}
|
||||
pub enum Side {
|
||||
Left,
|
||||
Right,
|
||||
}
|
||||
|
||||
impl Display for Side {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
@@ -19,17 +23,19 @@ impl Display for Side {
|
||||
impl Side {
|
||||
pub fn opposite(&self) -> Self {
|
||||
match self {
|
||||
Self::Left => Self::Right,
|
||||
Self::Right => Self::Left
|
||||
Self::Left => Self::Right,
|
||||
Self::Right => Self::Left,
|
||||
}
|
||||
}
|
||||
/// Shorthand for opposite
|
||||
pub fn inv(&self) -> Self { self.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..]
|
||||
Side::Right => &slice[slice.len() - size..],
|
||||
}
|
||||
}
|
||||
/// ignore N elements from this end of a slice
|
||||
@@ -38,8 +44,11 @@ impl Side {
|
||||
}
|
||||
/// 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]
|
||||
pub fn crop_both<'a, T>(
|
||||
&self,
|
||||
margin: usize,
|
||||
opposite: usize,
|
||||
slice: &'a [T],
|
||||
) -> &'a [T] {
|
||||
self.crop(margin, self.opposite().crop(opposite, slice))
|
||||
}
|
||||
@@ -47,21 +56,22 @@ impl Side {
|
||||
pub fn pick<T>(&self, pair: (T, T)) -> T {
|
||||
match self {
|
||||
Side::Left => pair.0,
|
||||
Side::Right => pair.1
|
||||
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)
|
||||
Side::Right => (opposite, this),
|
||||
}
|
||||
}
|
||||
/// Produces an increasing sequence on Right, and a decreasing sequence
|
||||
/// on Left
|
||||
pub fn walk<'a, I: DoubleEndedIterator + 'a>(&self, iter: I)
|
||||
-> BoxedIter<'a, I::Item>
|
||||
{
|
||||
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()),
|
||||
@@ -69,9 +79,18 @@ impl Side {
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -2,10 +2,13 @@ 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()};
|
||||
prefix.push(digits.chars().nth(val as usize - 1).unwrap_or_else(
|
||||
|| panic!("Overindexed digit set \"{}\" with {}", digits, val - 1)
|
||||
));
|
||||
} 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
|
||||
}
|
||||
|
||||
@@ -13,4 +16,4 @@ fn string_from_charset_rec(val: u64, digits: &str) -> String {
|
||||
/// characters
|
||||
pub fn string_from_charset(val: u64, digits: &str) -> String {
|
||||
string_from_charset_rec(val + 1, digits)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,28 +3,32 @@ use std::fmt::Debug;
|
||||
|
||||
// TODO: extract to crate
|
||||
|
||||
/// A FILO stack that lives on the regular call stack as a linked list.
|
||||
/// Mainly useful to detect loops in recursive algorithms where
|
||||
/// the recursion isn't deep enough to warrant a heap-allocated set.
|
||||
/// A frame of [Substack] which contains an element and a reference to the
|
||||
/// rest of the stack.
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Stackframe<'a, T> {
|
||||
pub item: T,
|
||||
pub prev: &'a Substack<'a, T>,
|
||||
pub len: usize
|
||||
pub len: usize,
|
||||
}
|
||||
|
||||
/// A FILO stack that lives on the regular call stack as a linked list.
|
||||
/// Mainly useful to detect loops in recursive algorithms where
|
||||
/// the recursion isn't deep enough to warrant a heap-allocated set.
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum Substack<'a, T> {
|
||||
Frame(Stackframe<'a, T>),
|
||||
Bottom
|
||||
Bottom,
|
||||
}
|
||||
|
||||
impl<'a, T> Substack<'a, T> {
|
||||
/// Convert the substack into an option of stackframe
|
||||
pub fn opt(&'a self) -> Option<&'a Stackframe<'a, T>> { match self {
|
||||
Self::Frame(f) => Some(f),
|
||||
Self::Bottom => None
|
||||
} }
|
||||
pub fn opt(&'a self) -> Option<&'a Stackframe<'a, T>> {
|
||||
match self {
|
||||
Self::Frame(f) => Some(f),
|
||||
Self::Bottom => None,
|
||||
}
|
||||
}
|
||||
/// Construct an iterator over the listlike, very fast O(1)
|
||||
pub fn iter(&self) -> SubstackIterator<T> {
|
||||
SubstackIterator { curr: self }
|
||||
@@ -33,22 +37,25 @@ impl<'a, T> Substack<'a, T> {
|
||||
Self::Frame(self.new_frame(item))
|
||||
}
|
||||
pub fn new_frame(&'a self, item: T) -> Stackframe<'a, T> {
|
||||
Stackframe {
|
||||
item,
|
||||
prev: self,
|
||||
len: self.opt().map_or(1, |s| s.len)
|
||||
}
|
||||
Stackframe { item, prev: self, len: self.opt().map_or(1, |s| s.len) }
|
||||
}
|
||||
pub fn pop(&'a self, count: usize) -> Option<&'a Stackframe<'a, T>> {
|
||||
if let Self::Frame(p) = self {
|
||||
if count == 0 {Some(p)}
|
||||
else {p.prev.pop(count - 1)}
|
||||
} else {None}
|
||||
if count == 0 {
|
||||
Some(p)
|
||||
} else {
|
||||
p.prev.pop(count - 1)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
pub fn len(&self) -> usize {
|
||||
match self {
|
||||
Self::Frame(f) => f.len,
|
||||
Self::Bottom => 0,
|
||||
}
|
||||
}
|
||||
pub fn len(&self) -> usize { match self {
|
||||
Self::Frame(f) => f.len,
|
||||
Self::Bottom => 0
|
||||
} }
|
||||
}
|
||||
|
||||
impl<'a, T: Debug> Debug for Substack<'a, T> {
|
||||
@@ -58,29 +65,31 @@ impl<'a, T: Debug> Debug for Substack<'a, T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterates over a substack from the top down
|
||||
pub struct SubstackIterator<'a, T> {
|
||||
curr: &'a Substack<'a, T>
|
||||
curr: &'a Substack<'a, T>,
|
||||
}
|
||||
|
||||
impl<'a, T> SubstackIterator<'a, T> {
|
||||
#[allow(unused)]
|
||||
pub fn first_some<U,
|
||||
F: Fn(&T) -> Option<U>
|
||||
>(&mut self, f: F) -> Option<U> {
|
||||
pub fn first_some<U, F: Fn(&T) -> Option<U>>(&mut self, f: F) -> Option<U> {
|
||||
for x in self.by_ref() {
|
||||
if let Some(result) = f(x) {
|
||||
return Some(result)
|
||||
return Some(result);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Returns an iterator that starts from the bottom of the stack
|
||||
/// and ends at the current position. This moves all items to the
|
||||
/// heap by copying them to a [Vec]
|
||||
pub fn rev_vec_clone(self) -> Vec<T> where T: Clone {
|
||||
/// and ends at the current position.
|
||||
pub fn rev_vec_clone(self) -> Vec<T>
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
let mut deque = VecDeque::with_capacity(self.curr.len());
|
||||
for item in self { deque.push_front(item.clone()) }
|
||||
for item in self {
|
||||
deque.push_front(item.clone())
|
||||
}
|
||||
deque.into()
|
||||
}
|
||||
}
|
||||
@@ -106,6 +115,3 @@ impl<'a, T> Iterator for SubstackIterator<'a, T> {
|
||||
(self.curr.len(), Some(self.curr.len()))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
/// A macro version of [Option::unwrap_or_else] which supports
|
||||
/// flow control statements such as `return` and `break` in the "else"
|
||||
/// branch.
|
||||
/// A macro version of [Option::unwrap_or_else] which supports flow
|
||||
/// control statements such as `return` and `break` in the "else" branch.
|
||||
#[macro_export]
|
||||
macro_rules! unwrap_or {
|
||||
($m:expr; $fail:expr) => {
|
||||
{ if let Some(res) = ($m) {res} else {$fail} }
|
||||
}
|
||||
}
|
||||
($m:expr; $fail:expr) => {{
|
||||
if let Some(res) = ($m) { res } else { $fail }
|
||||
}};
|
||||
}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
// trait Var<T> {
|
||||
// type With<U>: Var<U>;
|
||||
|
||||
// fn map<U>(self, f: impl FnOnce(T) -> U) -> Self::With<U>;
|
||||
// fn map_multi<U, V, Ret: Var<U> + Var<V>>(
|
||||
// self, f: impl FnOnce(T) -> Ret
|
||||
// ) -> <Self::With<U> as Var<U>>::With<V>;
|
||||
// }
|
||||
|
||||
// enum Variant<T, U> {
|
||||
// Head(T),
|
||||
// Tail(U)
|
||||
// }
|
||||
|
||||
// impl<H, T: Var<_>> Var<H> for Variant<H, T> {
|
||||
// fn map<U>(self, f: impl FnOnce(H) -> U) -> Self::With<U> {
|
||||
// match
|
||||
// }
|
||||
// }
|
||||
@@ -1,14 +1,16 @@
|
||||
/// Imitates a regular for loop with an exit clause using Rust's `loop` keyword.
|
||||
/// This macro brings the break value to all existing Rust loops, by allowing you to specify
|
||||
/// an exit expression in case the loop was broken by the condition and not an explicit `break`.
|
||||
///
|
||||
/// Since the exit expression can also be a block, this also allows you to execute other code when
|
||||
/// the condition fails. This can also be used to re-enter the loop with an explicit `continue`
|
||||
/// statement.
|
||||
///
|
||||
/// The macro also adds support for classic for loops familiar to everyone since C, except with
|
||||
/// the addition of an exit statement these too can be turned into expressions.
|
||||
///
|
||||
/// Imitates a regular for loop with an exit clause using Rust's `loop`
|
||||
/// keyword. This macro brings the break value to all existing Rust loops,
|
||||
/// by allowing you to specify an exit expression in case the loop was
|
||||
/// broken by the condition and not an explicit `break`.
|
||||
///
|
||||
/// Since the exit expression can also be a block, this also allows you to
|
||||
/// execute other code when the condition fails. This can also be used to
|
||||
/// re-enter the loop with an explicit `continue` statement.
|
||||
///
|
||||
/// The macro also adds support for classic for loops familiar to everyone
|
||||
/// since C, except with the addition of an exit statement these too can
|
||||
/// be turned into expressions.
|
||||
///
|
||||
/// ```
|
||||
/// xloop!(for i in 0..10; {
|
||||
/// connection.try_connect()
|
||||
@@ -17,9 +19,10 @@
|
||||
/// }
|
||||
/// }; None)
|
||||
/// ```
|
||||
///
|
||||
/// While loop with reentry. This is a very convoluted example but displays the idea quite clearly.
|
||||
///
|
||||
///
|
||||
/// While loop with reentry. This is a very convoluted example but
|
||||
/// displays the idea quite clearly.
|
||||
///
|
||||
/// ```
|
||||
/// xloop!(while socket.is_open(); {
|
||||
/// let (data, is_end) = socket.read();
|
||||
@@ -35,19 +38,19 @@
|
||||
/// }
|
||||
/// })
|
||||
/// ```
|
||||
///
|
||||
///
|
||||
/// CUDA algorythm for O(log n) summation using a C loop
|
||||
///
|
||||
///
|
||||
/// ```
|
||||
/// xloop!(let mut leap = 1; own_id*2 + leap < batch_size; leap *= 2; {
|
||||
/// batch[own_id*2] += batch[own_id*2 + leap]
|
||||
/// })
|
||||
/// ```
|
||||
///
|
||||
/// The above loop isn't used as an expression, but an exit expression - or block - can be added
|
||||
/// to these as well just like the others. In all cases the exit expression is optional, its
|
||||
/// default value is `()`.
|
||||
///
|
||||
///
|
||||
/// The above loop isn't used as an expression, but an exit expression -
|
||||
/// or block - can be added to these as well just like the others. In all
|
||||
/// cases the exit expression is optional, its default value is `()`.
|
||||
///
|
||||
/// TODO: find a valid use case for While let for a demo
|
||||
/// TODO: break out into crate
|
||||
#[macro_export]
|
||||
@@ -89,4 +92,4 @@ macro_rules! xloop {
|
||||
($init:stmt; $cond:expr; $step:stmt; $body:stmt; $exit:stmt) => {
|
||||
{ $init; xloop!(while $cond; { $body; $step }; $exit) }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user