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