light_ranged_integers 0.1.1

A Rust library similar to ranged_integers, a bit limited to work on stable rust
Documentation
/*
Copyright © 2024 - Massimo Gismondi

This file is part of light-ranged-integers.

light-ranged-integers is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

light-ranged-integers is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with light-ranged-integers.  If not, see <http://www.gnu.org/licenses/>.
*/

use crate::op_mode;
use paste::paste;
use std::marker::PhantomData;

macro_rules! gen_ranged_structs {
    ($i:ty) => {
    paste!{
        #[derive(Debug, Clone, Copy)]
        #[doc = "A ranged integer between MIN and MAX, encapsulating [" $i "] type.\n\n"]
        ///
        /// The library offers Range types, declared in this form
        /// ```rust,ignore
        #[doc=[<Ranged $i:upper>]"<MIN, MAX, OpMode>"]
        /// ```
        /// where:
        /// - `MIN` is a lower limit of the interval
        /// - `MAX` is the upper limit of the interval
        /// - `OpMode` selects how to behave when a number goes out of range. Have a
        ///    look at [op_mode] for all available modes.
        /// ```rust
        /// use light_ranged_integers::{RangedU16, op_mode::Panic};
        ///
        /// // Creating a Ranged value of 3, in the interval from 1 to 6.
        /// let n1 = RangedU16::<1,6, Panic>::new(3);
        ///
        /// ```
        /// The interval must be valid, as the MIN limit must be smaller than MAX.
        /// This is checked at compile time.
        /// ```rust,compile_fail
        /// # use light_ranged_integers::RangedU8;
        /// RangedU8::<2,1>::new(2);
        /// ```
        ///
        /// It will fail to compile even if the limits are the same
        /// ```rust,compile_fail
        /// # use light_ranged_integers::RangedU8;
        /// RangedU8::<2,2>::new(2);
        /// ```
        ///
        /// Pay attention that the code at compile time will check only the validity
        /// of the interval, but the check of the provided value fitting the interval is done at runtime.
        /// If you desire to move that check at compile time too, have a look at [Self::const_new()](Self::const_new()) constructor.
        pub struct [<Ranged $i:upper>]<const MIN: $i = {$i::MIN}, const MAX:$i = {$i::MAX}, OpMode=op_mode::Panic>($i, PhantomData<OpMode>) where OpMode: op_mode::OpModeExt;

        // Shortcuts
        #[doc = "A ranged integer between the provided MIN and [" $i "::MAX].\n\n"]
        pub type [<Ranged $i:upper Min>]<const MIN:$i, OpMode=op_mode::Panic> = [<Ranged $i:upper>]<MIN, {$i::MAX}, OpMode>;
        #[doc = "A ranged integer between [" $i "::MIN] and the provided MAX.\n\n"]
        pub type [<Ranged $i:upper Max>]<const MAX:$i, OpMode=op_mode::Panic> = [<Ranged $i:upper>]<{$i::MIN}, MAX, OpMode>;

        // Implement methods common for all modes
        impl<OpMode, const MIN: $i, const MAX:$i> [<Ranged $i:upper>]<MIN, MAX, OpMode> where OpMode: op_mode::OpModeExt
        {
            const COMPTIME_RANGE_CHECK: () = assert!(MIN < MAX, "INVALID RANGE. MIN must be smaller than MAX");

            #[doc="Builds a new [" [<Ranged $i:upper>]"] checking the limits at runtime"]
            ///
            /// The validity of the range will be checked at compile time,
            /// but it will check the value fits the range at runtime
            pub fn new(v: $i) -> Self
            {
                let _ = Self::COMPTIME_RANGE_CHECK;
                assert!(MIN<MAX);
                let v = OpMode::bring_in_range(v, MIN, MAX);
                Self(v, PhantomData)
            }

            /// This constructor will check both range validity and that the value is in the range
            /// at Compile time.
            ///
            /// The `V` number MUST be in range to compile correctly.
            ///
            /// For example, this code will compile just fine:
            /// ```
            #[doc="use light_ranged_integers::"[<Ranged $i:upper>]";"]
            #[doc="let m = "[<Ranged $i:upper>]"::<1,4>::const_new::<2>();"]
            /// ```
            ///
            /// But this one will not compile, as 5 is outside of \[1,4\]
            ///  ```rust,compile_fail
            #[doc="use light_ranged_integers::"[<Ranged $i:upper>]";"]
            #[doc="let m = "[<Ranged $i:upper>]"::<1,4>::const_new::<5>();"]
            /// ```
            pub const fn const_new<const V: $i>() -> [<Ranged $i:upper>]::<MIN, MAX, OpMode>
            {
                const_ranged::[<ConstRanged $i:upper>]::<MIN, MAX, V>::new().as_ranged::<OpMode>()
            }


            #[doc="Tries to construct a "[<Ranged $i:upper>]""]
            ///
            /// Checks the range during initialization.
            /// If the value fits the range, returns a new Ranged value,
            /// otherwise returns None
            pub fn maybe_new(value: $i) -> Option<Self>
            {
                if value >= MIN && value <= MAX
                {
                    return Some(Self::new(value))
                }
                else
                {
                    return None
                }
            }


            #[doc="Limits the value to a new minimum and maximum."]
            pub fn limit<const NEW_MIN: $i, const NEW_MAX: $i>(self) -> [<Ranged $i:upper>]<NEW_MIN, NEW_MAX, OpMode>
            {
                [<Ranged $i:upper>]::<NEW_MIN, NEW_MAX, OpMode>::new(self.0)
            }

            #[doc="Limits the value to a new minimum; keeps the previous maximum value limit."]
            pub fn limit_min<const NEW_MIN:$i>(self) -> [<Ranged $i:upper>]<NEW_MIN, MAX, OpMode>
            {
                self.limit::<NEW_MIN, MAX>()
            }

            #[doc="Limits the value to a new maximum; keeps the previous minimum value limit."]
            pub fn limit_max<const NEW_MAX:$i>(self) -> [<Ranged $i:upper>]<MIN, NEW_MAX, OpMode>
            {
                self.limit::<MIN, NEW_MAX>()
            }

            /// Returns the inner value alone
            pub const fn inner(&self) -> $i {self.0}
        }

        impl<OpMode, const MIN: $i, const MAX:$i> std::cmp::PartialEq<$i> for [<Ranged $i:upper>]<MIN, MAX, OpMode> where OpMode: op_mode::OpModeExt
        {
            fn eq(&self, other: &$i) -> bool
            {
                self.0.eq(other)
            }
        }
        impl<OpMode, const MIN: $i, const MAX:$i> std::cmp::PartialEq for [<Ranged $i:upper>]<MIN, MAX, OpMode> where OpMode: op_mode::OpModeExt
        {
            fn eq(&self, other: &[<Ranged $i:upper>]<MIN, MAX, OpMode>) -> bool
            {
                self.0.eq(&other.0)
            }
        }
        impl<OpMode, const MIN: $i, const MAX:$i> std::cmp::PartialOrd<$i> for [<Ranged $i:upper>]<MIN, MAX, OpMode> where OpMode: op_mode::OpModeExt
        {
            fn partial_cmp(&self, other: &$i) -> Option<std::cmp::Ordering>
            {
                self.0.partial_cmp(other)
            }
        }
        impl<OpMode, const MIN: $i, const MAX:$i> std::cmp::PartialOrd for [<Ranged $i:upper>]<MIN, MAX, OpMode> where OpMode: op_mode::OpModeExt
        {
            fn partial_cmp(&self, other: &[<Ranged $i:upper>]<MIN, MAX, OpMode>) -> Option<std::cmp::Ordering>
            {
                self.0.partial_cmp(&other.0)
            }
        }

        impl<OpMode, const MIN: $i, const MAX:$i> From<$i> for [<Ranged $i:upper>]<MIN,MAX,OpMode>
            where OpMode: op_mode::OpModeExt
        {
            fn from(value: $i) -> Self {
                Self::new(value)
            }
        }
    }
    };
    }

gen_ranged_structs!(u8);
gen_ranged_structs!(u16);
gen_ranged_structs!(u32);
gen_ranged_structs!(u64);
gen_ranged_structs!(i8);
gen_ranged_structs!(i16);
gen_ranged_structs!(i32);
gen_ranged_structs!(i64);

/// Compile time checked ranges
pub mod const_ranged
{
    use super::*;
    use crate::op_mode;
    use paste::paste;
    macro_rules! gen_const_ranged_structs {
        ($i:ty) => {
        paste!{
        #[derive(Clone, Copy)]
        #[doc = "A compile-time checked [" [<Ranged $i:upper>] "] struct.\n\n"]
        pub struct [<ConstRanged $i:upper>]<const MIN:$i, const MAX:$i, const V: $i>;
        impl<const MIN:$i, const MAX:$i, const V: $i> [<ConstRanged $i:upper>]<MIN,MAX,V>
        {
            const RANGE_VALID: () = assert!(MIN<MAX, "The range is not valid. MIN must be smaller than MAX");
            const IS_IN_RANGE: () = assert!(V>=MIN && V<=MAX, "Provided value V is outside of the range");

            /// Creates a new compile-checked range number.
            /// The number MUST be in range to compile correctly,
            /// independently of the [OpMode](op_mode)
            pub const fn new() -> Self
            {
                let _ = Self::RANGE_VALID;
                let _ = Self::IS_IN_RANGE;
                return [<ConstRanged $i:upper>]::<MIN,MAX,V>
            }

            /// Converts to a runtime Ranged value, assigning the provided OpMode
            pub const fn as_ranged<OpMode: op_mode::OpModeExt>(&self) -> [<Ranged $i:upper>]<MIN,MAX,OpMode>
            {
                return [<Ranged $i:upper>]::<MIN,MAX,OpMode>(V, PhantomData)
            }

            /// Returns the inner value alone
            pub const fn inner(self) -> $i
            {
                V
            }

            pub const fn get_limits(self) -> ($i,$i)
            {
                (MIN,MAX)
            }

            #[doc="Limits the value to a new minimum and maximum."]
            ///
            /// Makes sure the new limits will be respected
            /// ```
            /// use light_ranged_integers::const_ranged::*;
            /// // Value 5 fits [0,10], and also  [0,5]
            #[doc=""[<ConstRanged $i:upper>]"::<0,10,5>::new().limit::<0,5>();"]
            /// ```
            ///
            ///
            /// Raises a compile error if the value does not fit the new limit
            /// ```compile_fail
            /// use light_ranged_integers::const_ranged::*;
            /// // Value 5 fits [0,10], but not [0,3]
            #[doc=""[<ConstRanged $i:upper>]"::<0,10,5>::new().limit::<0,3>();"]
            /// ```
            pub const fn limit<const NEW_MIN:$i, const NEW_MAX:$i>(self) -> [<ConstRanged $i:upper>]::<NEW_MIN, NEW_MAX,V>
            {
                return [<ConstRanged $i:upper>]::new()
            }

            #[doc="Limits the value to a new minimum; keeps the previous maximum value limit."]
            pub const fn limit_min<const NEW_MIN:$i>(self) -> [<ConstRanged $i:upper>]::<NEW_MIN, MAX,V>
            {
                self.limit::<NEW_MIN, MAX>()
            }

            #[doc="Limits the value to a new maximum; keeps the previous minimum value limit."]
            pub const fn limit_max<const NEW_MAX:$i>(self) -> [<ConstRanged $i:upper>]::<MIN, NEW_MAX,V>
            {
                self.limit::<MIN, NEW_MAX>()
            }
        }
        }
    }}

    gen_const_ranged_structs!(u8);
    gen_const_ranged_structs!(u16);
    gen_const_ranged_structs!(u32);
    gen_const_ranged_structs!(u64);
    gen_const_ranged_structs!(i8);
    gen_const_ranged_structs!(i16);
    gen_const_ranged_structs!(i32);
    gen_const_ranged_structs!(i64);
}