const_format/__str_methods/
str_indexing.rs

1pub struct StrIndexArgsConv<T> {
2    pub str: &'static str,
3    pub arg: T,
4}
5
6#[allow(non_snake_case)]
7pub const fn StrIndexArgsConv<T>(str: &'static str, arg: T) -> StrIndexArgsConv<T> {
8    StrIndexArgsConv { str, arg }
9}
10
11pub struct StrIndexArgs {
12    pub str: &'static str,
13    pub index_validity: IndexValidity,
14    pub used_rstart: usize,
15    pub used_rlen: usize,
16    pub used_rend: usize,
17}
18
19#[derive(Copy, Clone)]
20#[cfg_attr(test, derive(Debug, PartialEq))]
21pub enum IndexValidity {
22    Valid,
23    StartOob(usize),
24    StartInsideChar(usize),
25    EndOob(usize),
26    EndInsideChar(usize),
27}
28
29impl IndexValidity {
30    pub const fn is_valid(self) -> bool {
31        matches!(self, Self::Valid)
32    }
33
34    pub const fn assert_valid(self) {
35        match self {
36            Self::Valid => (),
37            Self::StartOob(index) => [/*start index is out of bounds*/][index],
38            Self::StartInsideChar(index) => [/*start index is not on a char boundary*/][index],
39            Self::EndOob(index) => [/*end index is out of bounds*/][index],
40            Self::EndInsideChar(index) => [/*end index is not on a char boundary*/][index],
41        }
42    }
43}
44
45macro_rules! pass_range_types {
46    ($macro:ident) => {
47        const _: () = {
48            use core::ops;
49
50            #[allow(unused_imports)]
51            use crate::__hidden_utils::{is_char_boundary_no_len_check, max_usize, saturating_add};
52
53            $macro! {
54                fn(self, usize) {
55                    let mut end = saturating_add(self.arg, 1);
56                    let bytes = self.str.as_bytes();
57
58                    if end < self.str.len() {
59                        while !is_char_boundary_no_len_check(bytes, end) {
60                            end = saturating_add(end, 1);
61                        }
62                    }
63
64                    self.arg .. end
65                }
66
67                fn(self, ops::Range<usize>) {
68                    let ops::Range{start, end} = self.arg;
69                    start .. max_usize(start, end)
70                }
71
72                fn(self, ops::RangeTo<usize>) {
73                    0..self.arg.end
74                }
75
76                fn(self, ops::RangeFrom<usize>) {
77                    self.arg.start..self.str.len()
78                }
79
80                fn(self, ops::RangeInclusive<usize>) {
81                    let start = *self.arg.start();
82                    start .. max_usize(saturating_add(*self.arg.end(), 1), start)
83                }
84
85                fn(self, ops::RangeToInclusive<usize>) {
86                    0 .. saturating_add(self.arg.end, 1)
87                }
88
89                fn(self, ops::RangeFull) {
90                    0 .. self.str.len()
91                }
92            }
93        };
94    };
95}
96pub(super) use pass_range_types;
97
98macro_rules! define_conversions {
99    (
100        $( fn($self:ident, $ty:ty) $block:block )*
101    ) => {
102
103        $(
104            impl StrIndexArgsConv<$ty> {
105                pub const fn conv($self) -> StrIndexArgs {
106                    use crate::__hidden_utils::is_char_boundary_no_len_check;
107
108                    let range = $block;
109
110                    let str_len = $self.str.len();
111
112                    let mut used_rstart = 0;
113                    let mut used_rend = str_len;
114
115                    let mut index_validity = IndexValidity::Valid;
116                    let bytes = $self.str.as_bytes();
117
118                    if range.end > str_len {
119                        index_validity = IndexValidity::EndOob(range.end);
120                    } else if is_char_boundary_no_len_check(bytes, range.end) {
121                        used_rend = range.end;
122                    } else {
123                        index_validity = IndexValidity::EndInsideChar(range.end);
124                    };
125
126                    if range.start > str_len {
127                        index_validity = IndexValidity::StartOob(range.start);
128                    } else if is_char_boundary_no_len_check(bytes, range.start) {
129                        used_rstart = range.start;
130                    } else {
131                        index_validity = IndexValidity::StartInsideChar(range.start);
132                    };
133
134                    StrIndexArgs {
135                        str: $self.str,
136                        index_validity,
137                        used_rstart,
138                        used_rend,
139                        used_rlen: used_rend - used_rstart,
140                    }
141                }
142            }
143        )*
144    };
145}
146
147pass_range_types! {define_conversions}
148
149#[cfg(test)]
150mod tests {
151    use super::*;
152
153    #[test]
154    fn index_validity_test() {
155        macro_rules! miv {
156            ($str:expr, $range:expr) => {
157                StrIndexArgsConv($str, $range).conv().index_validity
158            };
159        }
160
161        assert_eq!(miv!("効率的", 3), IndexValidity::Valid);
162        assert_eq!(miv!("効率的", 6), IndexValidity::Valid);
163        assert_eq!(miv!("効率的", 3..6), IndexValidity::Valid);
164
165        assert_eq!(miv!("効率的", 4..6), IndexValidity::StartInsideChar(4));
166        assert_eq!(miv!("効率的", 3..5), IndexValidity::EndInsideChar(5));
167        assert_eq!(miv!("効率的", 7..9), IndexValidity::StartInsideChar(7));
168
169        assert_eq!(miv!("効率的", 100..9), IndexValidity::StartOob(100));
170        assert_eq!(miv!("効率的", 3..10), IndexValidity::EndOob(10));
171        assert_eq!(miv!("効率的", 9), IndexValidity::EndOob(10));
172        assert_eq!(miv!("効率的", 10), IndexValidity::StartOob(10));
173        assert_eq!(miv!("効率的", 100..900), IndexValidity::StartOob(100));
174    }
175}