Preparation for sharing

- rustfmt
- clippy
- comments
- README
This commit is contained in:
2023-05-25 19:14:24 +01:00
parent e99ade92ba
commit bc2714aad8
144 changed files with 3734 additions and 3243 deletions

View File

@@ -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()
}
}
}

View File

View File

@@ -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())
}
}

View File

@@ -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;

View File

@@ -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(", ")
}

View File

@@ -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),*])
};
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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

View File

@@ -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)
}
}

View File

@@ -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()))
}
}

View File

@@ -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 }
}};
}

View File

@@ -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
// }
// }

View File

@@ -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) }
};
}
}