Skip to main content

const_panic/
utils.rs

1//! Utility functions
2
3use crate::debug_str_fmt::ForEscaping;
4
5#[cfg(feature = "rust_1_64")]
6#[cfg(test)]
7mod utils_1_64_tests;
8
9#[cfg(test)]
10mod usize_parsing_tests;
11
12#[cfg(feature = "non_basic")]
13mod non_basic_utils;
14
15#[cfg(feature = "non_basic")]
16#[cfg_attr(feature = "docsrs", doc(cfg(feature = "non_basic")))]
17pub use self::non_basic_utils::*;
18
19/// Computes the minimum of `l` and `r`
20pub const fn min_usize(l: usize, r: usize) -> usize {
21    if l < r {
22        l
23    } else {
24        r
25    }
26}
27
28/// Computes the maximum of `l` and `r`
29pub const fn max_usize(l: usize, r: usize) -> usize {
30    if l > r {
31        l
32    } else {
33        r
34    }
35}
36
37#[derive(Copy, Clone)]
38pub(crate) struct TailShortString<const LEN: usize> {
39    start: u8,
40    buffer: [u8; LEN],
41}
42
43pub(crate) type PreFmtString = TailShortString<{ string_cap::PREFMT }>;
44
45pub(crate) mod string_cap {
46    /// The capacity of a [`ShortString`](crate::fmt::ShortString).
47    #[cfg(feature = "non_basic")]
48    pub const TINY: usize = 16;
49
50    // the TailShortString that's stored in PanicVal
51    pub(crate) const PREFMT: usize = 21;
52
53    // length of string to alternate binary format a 64 bit integer
54    pub(crate) const MEDIUM: usize = 66;
55
56    // length of string to alternate binary format a 128 bit integer
57    pub(crate) const LARGE: usize = 130;
58}
59
60impl<const LEN: usize> TailShortString<LEN> {
61    ///
62    /// # Safety
63    ///
64    /// `buffer` must be valid utf8 starting from the `start` index.
65    #[inline(always)]
66    pub(crate) const unsafe fn new(start: u8, buffer: [u8; LEN]) -> Self {
67        Self { start, buffer }
68    }
69
70    pub(crate) const fn len(&self) -> usize {
71        LEN - self.start as usize
72    }
73
74    pub(crate) const fn ranged(&self) -> RangedBytes<&[u8]> {
75        RangedBytes {
76            start: self.start as usize,
77            end: LEN,
78            bytes: &self.buffer,
79        }
80    }
81}
82
83////////////////////////////////////////////////////////
84
85#[repr(packed)]
86#[derive(Copy)]
87pub(crate) struct Packed<T>(pub(crate) T);
88
89impl<T: Copy> Clone for Packed<T> {
90    fn clone(&self) -> Self {
91        *self
92    }
93}
94
95////////////////////////////////////////////////////////
96
97#[derive(Copy, Clone)]
98pub(crate) struct RangedBytes<B> {
99    pub(crate) start: usize,
100    pub(crate) end: usize,
101    pub(crate) bytes: B,
102}
103
104impl<B> RangedBytes<B> {
105    pub(crate) const fn len(&self) -> usize {
106        self.end - self.start
107    }
108}
109impl RangedBytes<&'static [u8]> {
110    pub const EMPTY: Self = RangedBytes {
111        start: 0,
112        end: 0,
113        bytes: &[],
114    };
115}
116
117////////////////////////////////////////////////////////
118
119#[derive(Copy, Clone)]
120pub(crate) enum Sign {
121    Positive,
122    Negative = 1,
123}
124
125#[derive(Copy, Clone)]
126pub(crate) enum WasTruncated {
127    Yes(usize),
128    No,
129}
130
131impl WasTruncated {
132    pub(crate) const fn get_length(self, len: usize) -> usize {
133        match self {
134            WasTruncated::Yes(x) => x,
135            WasTruncated::No => len,
136        }
137    }
138}
139
140const fn is_char_boundary(b: u8) -> bool {
141    (b as i8) >= -0x40
142}
143
144// truncates a utf8-encoded string to the character before the `truncate_to` index
145//
146pub(crate) const fn truncated_str_len(
147    ranged: RangedBytes<&[u8]>,
148    truncate_to: usize,
149) -> WasTruncated {
150    if ranged.len() <= truncate_to {
151        WasTruncated::No
152    } else {
153        let mut i = ranged.start + truncate_to;
154        while i != ranged.start {
155            // if it's a non-continuation byte, break
156            if is_char_boundary(ranged.bytes[i]) {
157                break;
158            }
159            i -= 1;
160        }
161
162        WasTruncated::Yes(i - ranged.start)
163    }
164}
165
166pub(crate) const fn truncated_debug_str_len(
167    ranged: RangedBytes<&[u8]>,
168    truncate_to: usize,
169) -> WasTruncated {
170    let blen = ranged.end;
171
172    // `* 4` because the longest escape is written like `\xNN` which is 4 bytes
173    // `+ 2` for the quote characters
174    if blen * 4 + 2 <= truncate_to {
175        WasTruncated::No
176    } else if truncate_to == 0 {
177        WasTruncated::Yes(0)
178    } else {
179        let mut i = ranged.start;
180        // = 1 for opening quote char
181        let mut fmtlen = 1;
182        loop {
183            let next_i = next_char_boundary(ranged, min_usize(i + 1, ranged.end));
184
185            let mut j = i;
186            while j < next_i {
187                fmtlen += ForEscaping::byte_len(ranged.bytes[j]);
188                j += 1;
189            }
190
191            if fmtlen > truncate_to {
192                break;
193            } else if next_i == ranged.end {
194                i = next_i;
195                break;
196            } else {
197                i = next_i;
198            }
199        }
200
201        if i == blen && fmtlen < truncate_to {
202            WasTruncated::No
203        } else {
204            WasTruncated::Yes(i - ranged.start)
205        }
206    }
207}
208
209const fn next_char_boundary(ranged: RangedBytes<&[u8]>, mut i: usize) -> usize {
210    while i < ranged.end && !is_char_boundary(ranged.bytes[i]) {
211        i += 1;
212    }
213    i
214}
215
216#[cfg_attr(feature = "test", derive(Debug, PartialEq))]
217pub(crate) struct StartAndBytes<const LEN: usize> {
218    pub start: u8,
219    pub bytes: [u8; LEN],
220}
221
222#[track_caller]
223pub(crate) const fn tail_byte_array<const TO: usize>(
224    len: usize,
225    input: &[u8],
226) -> StartAndBytes<TO> {
227    assert!(len <= TO);
228
229    let mut bytes = [0u8; TO];
230
231    let start = TO - len;
232    let mut i = start;
233    let mut j = 0;
234    while j < len {
235        bytes[i] = input[j];
236        i += 1;
237        j += 1;
238    }
239
240    assert!(start < 256);
241    StartAndBytes {
242        start: start as u8,
243        bytes,
244    }
245}
246
247////////////////////////////////////////////////////////////////////////////////
248
249pub(crate) const fn parse_usize(input: &str) -> Option<usize> {
250    #[cfg(not(feature = "rust_1_82"))]
251    {
252        use crate::macros::unwrapping::try_opt;
253
254        if input.is_empty() {
255            return None;
256        }
257
258        let mut input = input.as_bytes();
259
260        let mut ret = 0usize;
261
262        while let [b, ref rem @ ..] = *input {
263            ret = try_opt!(ret.checked_mul(10));
264
265            ret = match b {
266                b'0'..=b'9' => try_opt!(ret.checked_add((b - b'0') as usize)),
267                _ => return None,
268            };
269
270            input = rem;
271        }
272
273        Some(ret)
274    }
275
276    #[cfg(feature = "rust_1_82")]
277    {
278        match usize::from_str_radix(input, 10) {
279            Ok(x) => Some(x),
280            Err(_) => None,
281        }
282    }
283}
284
285////////////////////////////////////////////////////////////////////////////////
286
287/// Const equivalent of `&buffer[..upto]` with saturating indexing.
288///
289/// "saturating indexing" means that if `upto > buffer.len()`,
290/// then this returns all of `buffer` instead of panicking.
291///
292/// # Example
293///
294/// ```rust
295/// use const_panic::utils::bytes_up_to;
296///
297/// const BYTES: &[u8] = &[3, 5, 8, 13, 21, 34, 55, 89];
298///
299/// const SLICE: &[u8] = bytes_up_to(BYTES, 4);
300/// assert_eq!(SLICE, &[3, 5, 8, 13][..]);
301///
302/// const WHOLE: &[u8] = bytes_up_to(BYTES, usize::MAX);
303/// assert_eq!(WHOLE, &[3, 5, 8, 13, 21, 34, 55, 89][..]);
304///
305/// ```
306pub const fn bytes_up_to(buffer: &[u8], upto: usize) -> &[u8] {
307    if upto > buffer.len() {
308        return buffer;
309    }
310
311    #[cfg(not(feature = "rust_1_64"))]
312    {
313        let mut to_truncate = buffer.len() - upto;
314        let mut out: &[u8] = buffer;
315
316        while to_truncate != 0 {
317            if let [rem @ .., _] = out {
318                out = rem;
319            }
320            to_truncate -= 1;
321        }
322
323        if out.len() != upto {
324            panic!("BUG!")
325        }
326
327        out
328    }
329
330    #[cfg(feature = "rust_1_64")]
331    {
332        // SAFETY: the above conditional ensures that `upto` doesn't
333        // create a partially-dangling slice
334        unsafe { core::slice::from_raw_parts(buffer.as_ptr(), upto) }
335    }
336}