Skip to main content

endbasic_core/
num.rs

1// EndBASIC
2// Copyright 2026 Julio Merino
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU Affero General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program.  If not, see <https://www.gnu.org/licenses/>.
16
17//! Efficient conversion utilities with diagnostics.
18
19use std::convert::TryFrom;
20use std::num::TryFromIntError;
21
22/// An unsigned integer constrained to 24 bits.
23#[derive(Clone, Copy, Debug, Eq, PartialEq)]
24pub struct U24(u32);
25
26impl From<u16> for U24 {
27    fn from(value: u16) -> Self {
28        Self(u32::from(value))
29    }
30}
31
32impl TryFrom<u32> for U24 {
33    type Error = ();
34
35    fn try_from(value: u32) -> Result<Self, Self::Error> {
36        if value >= (1 << 24) { Err(()) } else { Ok(Self(value)) }
37    }
38}
39
40impl TryFrom<usize> for U24 {
41    type Error = ();
42
43    fn try_from(value: usize) -> Result<Self, Self::Error> {
44        if value >= (1 << 24) { Err(()) } else { Ok(Self(value as u32)) }
45    }
46}
47
48impl From<U24> for u32 {
49    fn from(value: U24) -> Self {
50        value.0
51    }
52}
53
54impl TryFrom<U24> for usize {
55    type Error = TryFromIntError;
56
57    fn try_from(value: U24) -> Result<Self, Self::Error> {
58        Self::try_from(value.0)
59    }
60}
61
62impl U24 {
63    /// Maximum value that a `U24` can carry.
64    pub(crate) const MAX: U24 = U24(1 << 24);
65}
66
67/// A trait to perform an unchecked cast from one type to another.
68trait UncheckedFrom {
69    /// The source type to convert from.
70    type T;
71
72    /// Converts `value` to `Self`.
73    ///
74    /// Must be implemented as an `as` cast in release builds but can do extra
75    /// sanity-checking in debug builds.
76    fn unchecked_from(value: Self::T) -> Self;
77}
78
79/// Implements an unchecked conversion function between two integer types.
80///
81/// In debug mode, this asserts that the input value fits in the return type.
82/// In release mode, this makes the conversion with truncation.
83macro_rules! impl_unchecked_cast {
84    ( $name:ident, $from_ty:ty, $to_ty:ty, primitive ) => {
85        pub(crate) fn $name(value: $from_ty) -> $to_ty {
86            if cfg!(debug_assertions) {
87                <$to_ty>::try_from(value).unwrap()
88            } else {
89                value as $to_ty
90            }
91        }
92    };
93
94    ( $name:ident, $from_ty:ty, $to_ty:ty, user_defined ) => {
95        pub(crate) fn $name(value: $from_ty) -> $to_ty {
96            if cfg!(debug_assertions) {
97                <$to_ty>::try_from(value).unwrap()
98            } else {
99                <$to_ty>::unchecked_from(value)
100            }
101        }
102    };
103}
104
105impl_unchecked_cast!(unchecked_u32_as_u8, u32, u8, primitive);
106impl_unchecked_cast!(unchecked_u32_as_u16, u32, u16, primitive);
107impl_unchecked_cast!(unchecked_u32_as_usize, u32, usize, primitive);
108impl_unchecked_cast!(unchecked_u64_as_u8, u64, u8, primitive);
109impl_unchecked_cast!(unchecked_usize_as_u8, usize, u8, primitive);
110
111impl UncheckedFrom for usize {
112    type T = U24;
113
114    fn unchecked_from(value: Self::T) -> Self {
115        value.0 as Self
116    }
117}
118
119impl_unchecked_cast!(unchecked_u24_as_usize, U24, usize, user_defined);
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124
125    #[test]
126    fn test_unchecked_u32_as_u8() {
127        assert_eq!(10u8, unchecked_u32_as_u8(10u32));
128    }
129
130    #[test]
131    fn test_unchecked_u32_as_u16() {
132        assert_eq!(10u16, unchecked_u32_as_u16(10u32));
133    }
134
135    #[test]
136    fn test_unchecked_u32_as_usize() {
137        assert_eq!(10usize, unchecked_u32_as_usize(10u32));
138    }
139
140    #[test]
141    fn test_unchecked_u64_as_u8() {
142        assert_eq!(10u8, unchecked_u64_as_u8(10u64));
143    }
144
145    #[test]
146    fn test_unchecked_usize_as_u8() {
147        assert_eq!(10u8, unchecked_usize_as_u8(10_usize));
148    }
149
150    #[test]
151    fn test_unchecked_u24_as_usize() {
152        assert_eq!(10_usize, unchecked_u24_as_usize(U24(10)));
153    }
154}