Initial commit
This commit is contained in:
commit
37b3f2220c
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
Cargo.lock
|
21
Cargo.toml
Normal file
21
Cargo.toml
Normal file
|
@ -0,0 +1,21 @@
|
|||
[package]
|
||||
name = "stable-step"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
[[example]]
|
||||
name = "derive"
|
||||
required-features = ["derive"]
|
||||
|
||||
[dependencies]
|
||||
stable-step-derive = { version = "0.1", path = "./stable-step-derive/", optional = true }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
derive = ["stable-step-derive"]
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"stable-step-derive"
|
||||
]
|
61
examples/basic.rs
Normal file
61
examples/basic.rs
Normal file
|
@ -0,0 +1,61 @@
|
|||
use stable_step::{Step, StepExt};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
enum MyEnum {
|
||||
A,
|
||||
B,
|
||||
C,
|
||||
D,
|
||||
E,
|
||||
F,
|
||||
}
|
||||
|
||||
impl Step for MyEnum {
|
||||
const MIN: Self = Self::A;
|
||||
const MAX: Self = Self::F;
|
||||
|
||||
fn next(&self) -> Option<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
match self {
|
||||
Self::A => Some(Self::B),
|
||||
Self::B => Some(Self::C),
|
||||
Self::C => Some(Self::D),
|
||||
Self::D => Some(Self::E),
|
||||
Self::E => Some(Self::F),
|
||||
Self::F => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn prev(&self) -> Option<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
match self {
|
||||
Self::A => None,
|
||||
Self::B => Some(Self::A),
|
||||
Self::C => Some(Self::B),
|
||||
Self::D => Some(Self::C),
|
||||
Self::E => Some(Self::D),
|
||||
Self::F => Some(Self::E),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!("All");
|
||||
for value in MyEnum::iter() {
|
||||
println!("{:?}", value);
|
||||
}
|
||||
|
||||
println!("Subset");
|
||||
for value in MyEnum::B.iter_to(MyEnum::E) {
|
||||
println!("{:?}", value);
|
||||
}
|
||||
|
||||
println!("Reversed");
|
||||
for value in MyEnum::B.iter_to_inclusive(MyEnum::E).rev() {
|
||||
println!("{:?}", value);
|
||||
}
|
||||
}
|
28
examples/derive.rs
Normal file
28
examples/derive.rs
Normal file
|
@ -0,0 +1,28 @@
|
|||
use stable_step::{Step, StepExt};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Step)]
|
||||
enum MyEnum {
|
||||
A,
|
||||
B,
|
||||
C,
|
||||
D,
|
||||
E,
|
||||
F,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!("All");
|
||||
for value in MyEnum::iter() {
|
||||
println!("{:?}", value);
|
||||
}
|
||||
|
||||
println!("Subset");
|
||||
for value in MyEnum::B.iter_to(MyEnum::E) {
|
||||
println!("{:?}", value);
|
||||
}
|
||||
|
||||
println!("Reversed");
|
||||
for value in MyEnum::B.iter_to_inclusive(MyEnum::E).rev() {
|
||||
println!("{:?}", value);
|
||||
}
|
||||
}
|
268
src/lib.rs
Normal file
268
src/lib.rs
Normal file
|
@ -0,0 +1,268 @@
|
|||
use std::iter::FusedIterator;
|
||||
|
||||
#[cfg(feature = "derive")]
|
||||
pub use stable_step_derive::Step;
|
||||
|
||||
pub trait Step: PartialOrd {
|
||||
const MIN: Self;
|
||||
const MAX: Self;
|
||||
|
||||
fn next(&self) -> Option<Self>
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
fn prev(&self) -> Option<Self>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
pub trait StepExt: Step {
|
||||
fn iter() -> RangeIter<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
range(None, None)
|
||||
}
|
||||
|
||||
fn iter_to(self, to: Self) -> RangeIter<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
range(Some(self), Some(to))
|
||||
}
|
||||
|
||||
fn iter_to_inclusive(self, to: Self) -> RangeIter<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
range_inclusive(Some(self), Some(to))
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> StepExt for S where S: Step {}
|
||||
|
||||
pub struct RangeIter<S> {
|
||||
lower: Option<S>,
|
||||
upper: Option<S>,
|
||||
}
|
||||
|
||||
pub fn range_inclusive<S: Step>(from: Option<S>, to: Option<S>) -> RangeIter<S> {
|
||||
let from = match from {
|
||||
Some(lower) => lower,
|
||||
None => S::MIN,
|
||||
};
|
||||
|
||||
let to = match to {
|
||||
Some(upper) => upper,
|
||||
None => S::MAX,
|
||||
};
|
||||
|
||||
RangeIter {
|
||||
lower: Some(from),
|
||||
upper: Some(to),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn range<S: Step>(from: Option<S>, to: Option<S>) -> RangeIter<S> {
|
||||
let to = to.and_then(|to| to.prev());
|
||||
|
||||
range_inclusive(from, to)
|
||||
}
|
||||
|
||||
impl<S> Iterator for RangeIter<S>
|
||||
where
|
||||
S: Step,
|
||||
{
|
||||
type Item = S;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let current = self.lower.take()?;
|
||||
|
||||
let upper = self.upper.as_ref()?;
|
||||
|
||||
if ¤t > upper {
|
||||
return None;
|
||||
}
|
||||
|
||||
self.lower = current.next();
|
||||
|
||||
Some(current)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> DoubleEndedIterator for RangeIter<S>
|
||||
where
|
||||
S: Step,
|
||||
{
|
||||
fn next_back(&mut self) -> Option<Self::Item> {
|
||||
let current = self.upper.take()?;
|
||||
|
||||
let lower = self.lower.as_ref()?;
|
||||
|
||||
if ¤t < lower {
|
||||
return None;
|
||||
}
|
||||
|
||||
self.upper = current.prev();
|
||||
|
||||
Some(current)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> FusedIterator for RangeIter<S> where S: Step {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{range, range_inclusive, Step};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
enum Test {
|
||||
One,
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
}
|
||||
|
||||
impl Step for Test {
|
||||
const MIN: Self = Self::One;
|
||||
const MAX: Self = Self::Four;
|
||||
|
||||
fn next(&self) -> Option<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
match self {
|
||||
Self::One => Some(Self::Two),
|
||||
Self::Two => Some(Self::Three),
|
||||
Self::Three => Some(Self::Four),
|
||||
Self::Four => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn prev(&self) -> Option<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
match self {
|
||||
Self::One => None,
|
||||
Self::Two => Some(Self::One),
|
||||
Self::Three => Some(Self::Two),
|
||||
Self::Four => Some(Self::Three),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn range_none_none() {
|
||||
let v: Vec<Test> = range(None, None).collect();
|
||||
|
||||
assert_eq!(v, vec![Test::One, Test::Two, Test::Three, Test::Four]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn range_reverse_none_none() {
|
||||
let v: Vec<Test> = range(None, None).rev().collect();
|
||||
|
||||
assert_eq!(v, vec![Test::Four, Test::Three, Test::Two, Test::One]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn range_upper() {
|
||||
let v: Vec<Test> = range(None, Some(Test::Four)).collect();
|
||||
|
||||
assert_eq!(v, vec![Test::One, Test::Two, Test::Three]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn range_reverse_upper() {
|
||||
let v: Vec<Test> = range(None, Some(Test::Four)).rev().collect();
|
||||
|
||||
assert_eq!(v, vec![Test::Three, Test::Two, Test::One]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn range_lower() {
|
||||
let v: Vec<Test> = range(Some(Test::Two), None).collect();
|
||||
|
||||
assert_eq!(v, vec![Test::Two, Test::Three, Test::Four]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn range_reverse_lower() {
|
||||
let v: Vec<Test> = range(Some(Test::Two), None).rev().collect();
|
||||
|
||||
assert_eq!(v, vec![Test::Four, Test::Three, Test::Two]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn range_upper_lower() {
|
||||
let v: Vec<Test> = range(Some(Test::Two), Some(Test::Four)).collect();
|
||||
|
||||
assert_eq!(v, vec![Test::Two, Test::Three]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn range_reverse_upper_lower() {
|
||||
let v: Vec<Test> = range(Some(Test::Two), Some(Test::Four)).rev().collect();
|
||||
|
||||
assert_eq!(v, vec![Test::Three, Test::Two]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn range_inclusive_none_none() {
|
||||
let v: Vec<Test> = range_inclusive(None, None).collect();
|
||||
|
||||
assert_eq!(v, vec![Test::One, Test::Two, Test::Three, Test::Four]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn range_reverse_inclusive_none_none() {
|
||||
let v: Vec<Test> = range_inclusive(None, None).rev().collect();
|
||||
|
||||
assert_eq!(v, vec![Test::Four, Test::Three, Test::Two, Test::One]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn range_inclusive_upper() {
|
||||
let v: Vec<Test> = range_inclusive(None, Some(Test::Three)).collect();
|
||||
|
||||
assert_eq!(v, vec![Test::One, Test::Two, Test::Three]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn range_reverse_inclusive_upper() {
|
||||
let v: Vec<Test> = range_inclusive(None, Some(Test::Three)).rev().collect();
|
||||
|
||||
assert_eq!(v, vec![Test::Three, Test::Two, Test::One]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn range_inclusive_lower() {
|
||||
let v: Vec<Test> = range_inclusive(Some(Test::Two), None).collect();
|
||||
|
||||
assert_eq!(v, vec![Test::Two, Test::Three, Test::Four]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn range_reverse_inclusive_lower() {
|
||||
let v: Vec<Test> = range_inclusive(Some(Test::Two), None).rev().collect();
|
||||
|
||||
assert_eq!(v, vec![Test::Four, Test::Three, Test::Two]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn range_inclusive_upper_lower() {
|
||||
let v: Vec<Test> = range_inclusive(Some(Test::Two), Some(Test::Three)).collect();
|
||||
|
||||
assert_eq!(v, vec![Test::Two, Test::Three]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn range_reverse_inclusive_upper_lower() {
|
||||
let v: Vec<Test> = range_inclusive(Some(Test::Two), Some(Test::Three))
|
||||
.rev()
|
||||
.collect();
|
||||
|
||||
assert_eq!(v, vec![Test::Three, Test::Two]);
|
||||
}
|
||||
}
|
14
stable-step-derive/Cargo.toml
Normal file
14
stable-step-derive/Cargo.toml
Normal file
|
@ -0,0 +1,14 @@
|
|||
[package]
|
||||
name = "stable-step-derive"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "1"
|
||||
quote = "1"
|
||||
syn = "1"
|
89
stable-step-derive/src/lib.rs
Normal file
89
stable-step-derive/src/lib.rs
Normal file
|
@ -0,0 +1,89 @@
|
|||
use proc_macro::{self, TokenStream};
|
||||
use quote::quote;
|
||||
use syn::{parse_macro_input, Data, DeriveInput};
|
||||
|
||||
#[proc_macro_derive(Step)]
|
||||
pub fn derive(input: TokenStream) -> TokenStream {
|
||||
let DeriveInput {
|
||||
ident,
|
||||
generics,
|
||||
data,
|
||||
..
|
||||
} = parse_macro_input!(input);
|
||||
|
||||
let data = if let Data::Enum(data) = data {
|
||||
data
|
||||
} else {
|
||||
panic!("Step can only be derived for Enums");
|
||||
};
|
||||
|
||||
for variant in &data.variants {
|
||||
if !variant.fields.is_empty() {
|
||||
panic!("Step can only be derived for Enums with no variant fields");
|
||||
}
|
||||
}
|
||||
|
||||
if data.variants.is_empty() {
|
||||
panic!("Step cannot be derived for an empty Enum");
|
||||
}
|
||||
|
||||
let first = data.variants.iter().next().unwrap();
|
||||
let last = data.variants.iter().last().unwrap();
|
||||
|
||||
let nexts: proc_macro2::TokenStream = data
|
||||
.variants
|
||||
.iter()
|
||||
.zip(data.variants.iter().skip(1))
|
||||
.map(|(lesser, greater)| {
|
||||
let lesser = &lesser.ident;
|
||||
let greater = &greater.ident;
|
||||
quote! {
|
||||
Self::#lesser => Some(Self::#greater),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let prevs: proc_macro2::TokenStream = data
|
||||
.variants
|
||||
.iter()
|
||||
.zip(data.variants.iter().skip(1))
|
||||
.map(|(lesser, greater)| {
|
||||
let lesser = &lesser.ident;
|
||||
let greater = &greater.ident;
|
||||
quote! {
|
||||
Self::#greater => Some(Self::#lesser),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let (impl_generics, type_generics, where_clause) = generics.split_for_impl();
|
||||
|
||||
let output = quote! {
|
||||
impl #impl_generics Step for #ident #type_generics #where_clause {
|
||||
const MIN: Self = Self::#first;
|
||||
const MAX: Self = Self::#last;
|
||||
|
||||
fn next(&self) -> Option<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
match self {
|
||||
Self::#last => None,
|
||||
#nexts
|
||||
}
|
||||
}
|
||||
|
||||
fn prev(&self) -> Option<Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
match self {
|
||||
Self::#first => None,
|
||||
#prevs
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
output.into()
|
||||
}
|
Loading…
Reference in a new issue