sort_const/
lib.rs

1#![no_std]
2
3//! A macro for sorting arrays and slices at compile time.
4//!
5//! ## Usage
6//! 
7//! Just use the [`const_quicksort`] macro.
8//! 
9//! ```
10//! use sort_const::const_quicksort;
11//! 
12//! const fn lt(a: &u8, b: &u8) -> bool {
13//!     *a < *b
14//! }
15//! 
16//! const A: &[u8] = &const_quicksort!([3, 1, 2]);
17//! const B: &[u8] = &const_quicksort!([3, 1, 2], |a, b| *a < *b);
18//! const C: &[u8] = &const_quicksort!([3, 1, 2], lt);
19//! 
20//! assert_eq!(A, [1, 2, 3]);
21//! assert_eq!(B, [1, 2, 3]);
22//! assert_eq!(C, [1, 2, 3]);
23//! ```
24
25/// Used for our `call stack`
26#[doc(hidden)]
27pub use arrayvec_const::ArrayVec;
28
29/// Sorts a `const` array or mutable slice as a `const` expression.
30/// 
31/// Can be called with just the data to be sorted, in which case elements are compared by `a < b` resulting in the smallest element at
32/// the head of the data and the largest at the tail.
33/// 
34/// ```
35/// use sort_const::const_quicksort;
36/// 
37/// const U8S: &[u8] = &const_quicksort!([3, 1, 2]);
38/// 
39/// assert_eq!(U8S, [1, 2, 3]);
40/// ```
41/// 
42/// Can also be called with a lambda-like expression (e.g. `|a, b| a < b`) where `true` identifies that `a` should come before `b`.
43/// 
44/// ```
45/// use sort_const::const_quicksort;
46/// 
47/// #[derive(Debug, PartialEq)]
48/// struct Foo(u8);
49/// 
50/// const FOOS_MUT_REF: &[Foo] = &{
51///     let mut foo = [Foo(1), Foo(2), Foo(4), Foo(3)];
52///     const_quicksort!(foo.split_last_mut().expect("failed").1, |a, b| a.0 > b.0);
53///     foo
54/// };
55/// 
56/// assert_eq!(FOOS_MUT_REF, [4, 2, 1, 3].map(Foo));
57/// ```
58/// 
59/// Can also be called with the name of a `const` function which must return a boolean and which will be evaluated over `&data[a], &data[b]`.
60#[macro_export]
61macro_rules! const_quicksort {
62    ($data:expr) => {{
63        macro_rules! cmp {
64            ($a:expr, $b:expr) => {{ $a < $b }};
65        }
66        $crate::const_quicksort_adv!($data, cmp)
67    }};
68    ($data:expr, |$a:ident, $b:ident| $cmp:expr) => {{
69        macro_rules! cmp {
70            ($a_:expr, $b_:expr) => {{
71                let ($a, $b) = (&$a_, &$b_);
72                $cmp
73            }};
74        }
75        $crate::const_quicksort_adv!($data, cmp)
76    }};
77    ($data:expr, $cmp:expr) => {{
78        macro_rules! cmp {
79            ($a:expr, $b:expr) => {{ $cmp(&$a, &$b) }};
80        }
81        $crate::const_quicksort_adv!($data, cmp)
82    }};
83}
84
85
86/// Sorts a `const` array or mutable slice as a `const` expression.
87/// 
88/// The first parameter is the data to be sorted.
89/// 
90/// The second parameter is the path identifying the macro which should be invoked to compare elements `cmp!(a, b)`
91#[macro_export]
92macro_rules! const_quicksort_adv {
93    ($data:expr, $cmp:path) => {{
94        use core::mem::forget;
95
96        struct Wrapper<T>(T);
97        impl<T, const N: usize> Wrapper<[T; N]> {
98            #[allow(unused)]
99            const fn as_mut_slice(&mut self) -> &mut [T] {
100                &mut self.0
101            }
102        }
103        impl<'a, T> Wrapper<&'a mut [T]> {
104            #[allow(unused)]
105            const fn as_mut_slice(&mut self) -> &mut [T] {
106                self.0
107            }
108        }
109        impl<'a, T, const N: usize> Wrapper<&'a mut [T; N]> {
110            #[allow(unused)]
111            const fn as_mut_slice(&mut self) -> &mut [T] {
112                self.0
113            }
114        }
115
116        let mut data = Wrapper($data);
117        // 2^64 elements should be plenty
118        let mut slices = $crate::ArrayVec::<&mut [_], 64>::new();
119        let Ok(()) = slices.try_push(data.as_mut_slice()) else { panic!("ran out of capacity") };
120        while let Some(data) = slices.pop() {
121            match data.len() {
122                0 | 1 => continue,
123                2 => {
124                    if !{$cmp!(data[0], data[1])} {
125                        data.swap(0, 1);
126                    }
127                    continue;
128                },
129                _ => {}
130            }
131
132
133            let (pivot, rest) = data
134                .split_first_mut()
135                .expect("slice is not empty, as verified above");
136
137            let mut left = 0;
138            let mut right = rest.len() - 1;
139            while left <= right {
140                if {$cmp!(rest[left], *pivot)} {
141                    left += 1;
142                } else if !{$cmp!(rest[right], *pivot)} {
143                    if right == 0 {
144                        break;
145                    }
146                    right -= 1;
147                } else {
148                    rest.swap(left, right);
149                    left += 1;
150                    if right == 0 {
151                        break;
152                    }
153                    right -= 1;
154                }
155            }
156
157            data.swap(0, left);
158            let (left, right) = data.split_at_mut(left);
159            let Ok(()) = slices.try_push(left) else { panic!("ran out of capacity") };
160            if let Some((_pivot, right)) = right.split_first_mut() {
161                let Ok(()) = slices.try_push(right) else { panic!("ran out of capacity") };
162            }
163        }
164        forget(slices);
165
166        data.0
167    }};
168}
169
170#[cfg(test)]
171mod test {
172    #[derive(Debug)]
173    struct Foo(u8);
174
175    const fn lt(a: &u8, b: &u8) -> bool {
176        *a < *b
177    }
178
179    const U8S: &[u8] = &const_quicksort!([3, 2, 1]);
180    const U8S_FN: &[u8] = &const_quicksort!([3, 2, 1], lt);
181    const FOOS: &[Foo] = &const_quicksort!([Foo(1), Foo(2), Foo(4), Foo(3)], |a, b| a.0 > b.0);
182    const FOOS_MUT_REF: &[Foo] = &{
183        let mut foo = [Foo(1), Foo(2), Foo(4), Foo(3)];
184        const_quicksort!(foo.split_last_mut().expect("failed").1, |a, b| a.0 > b.0);
185        foo
186    };
187
188    #[test]
189    fn test_u8() {
190        assert_eq!(U8S, [1, 2, 3]);
191    }
192
193    #[test]
194    fn test_u8_fn() {
195        assert_eq!(U8S_FN, [1, 2, 3]);
196    }
197
198    #[test]
199    fn test_foo() {
200        assert!(FOOS.iter().map(|v| v.0).eq([4, 3, 2, 1]));
201        assert!(FOOS_MUT_REF.iter().map(|v| v.0).eq([4, 2, 1, 3]));
202    }
203}