Line | Branch | Decision | Exec | Source |
---|---|---|---|---|
1 | #pragma once | |||
2 | ||||
3 | #include <cstdint> | |||
4 | #include <string_view> | |||
5 | #include <algorithm> | |||
6 | ||||
7 | namespace rusefi::stringutil { | |||
8 | constexpr uint32_t DEFAULT_MAX_EXPECTED_STRING_LENGTH = 100; | |||
9 | ||||
10 | /****************************************************************/ | |||
11 | // TODO: refactor to constexpr | |||
12 | /****************************************************************/ | |||
13 | bool strEqualCaseInsensitive(const char *str1, const char *str2); | |||
14 | bool strEqual(const char *str1, const char *str2); | |||
15 | float atoff(const char*); | |||
16 | /****************************************************************/ | |||
17 | ||||
18 | namespace implementation { | |||
19 | // Internal implementation: pointer version with bounded length | |||
20 | template <size_t MaxLen> | |||
21 | 133 | constexpr uint32_t efiStrlen_impl(char const* const str) noexcept { | ||
22 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 133 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 133 times.
|
133 | if (str == nullptr) { |
23 | ✗ | return 0; | ||
24 | } | |||
25 | ||||
26 | 133 | size_t i{}; | ||
27 | ||||
28 |
3/4✓ Branch 0 taken 687 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 554 times.
✓ Branch 3 taken 133 times.
|
0/1? Decision couldn't be analyzed.
|
687 | while (i < MaxLen && str[i] != '\0') { |
29 | 554 | ++i; | ||
30 | } | |||
31 | ||||
32 | 133 | return static_cast<uint32_t>(i); | ||
33 | } | |||
34 | ||||
35 | template <size_t MaxLen> | |||
36 | 63 | constexpr int32_t indexOf_impl(char const* const string, char const ch) noexcept { | ||
37 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 63 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 63 times.
|
63 | if (string == nullptr) { |
38 | ✗ | return -1; | ||
39 | } | |||
40 | ||||
41 | 63 | uint32_t i = 0; | ||
42 |
1/2✓ Branch 0 taken 345 times.
✗ Branch 1 not taken.
|
1/2✓ Decision 'true' taken 345 times.
✗ Decision 'false' not taken.
|
345 | while (i < MaxLen) { |
43 | 345 | char c = string[i]; | ||
44 |
2/2✓ Branch 0 taken 40 times.
✓ Branch 1 taken 305 times.
|
2/2✓ Decision 'true' taken 40 times.
✓ Decision 'false' taken 305 times.
|
345 | if (c == '\0') { |
45 | 40 | return -1; | ||
46 | } | |||
47 |
2/2✓ Branch 0 taken 23 times.
✓ Branch 1 taken 282 times.
|
2/2✓ Decision 'true' taken 23 times.
✓ Decision 'false' taken 282 times.
|
305 | if (c == ch) { |
48 | 23 | return static_cast<int32_t>(i); | ||
49 | } | |||
50 | 282 | ++i; | ||
51 | } | |||
52 | ||||
53 | ✗ | return -1; | ||
54 | } | |||
55 | } | |||
56 | ||||
57 | // Generic version with default max length (for pointers) | |||
58 | template <size_t MaxLen> | |||
59 | constexpr uint32_t efiStrnlen(char const* const str) noexcept { | |||
60 | return implementation::efiStrlen_impl<MaxLen>(str); | |||
61 | } | |||
62 | ||||
63 | // Overload for native arrays: deduces size as max | |||
64 | template <size_t N> | |||
65 | constexpr uint32_t efiStrlen(char const (&arr)[N]) noexcept | |||
66 | requires (std::is_array_v<std::remove_cvref_t<decltype(arr)>> && N > 0) { | |||
67 | return implementation::efiStrlen_impl<N>(arr); | |||
68 | } | |||
69 | ||||
70 | // Counts up to DEFAULT_MAX_EXPECTED_STRING_LENGTH(100) characters in the provided string | |||
71 | // Use efiStrnlen<> to raise the limit | |||
72 | template <size_t MaxLen = DEFAULT_MAX_EXPECTED_STRING_LENGTH, typename T> | |||
73 | 133 | constexpr uint32_t efiStrlen(T str) noexcept | ||
74 | requires (!std::is_array_v<std::remove_cvref_t<T>>) { | |||
75 | 133 | return implementation::efiStrlen_impl<MaxLen>(str); | ||
76 | } | |||
77 | ||||
78 | template <size_t MaxLen, size_t N> | |||
79 | constexpr int32_t indexOfN(char const (&arr)[N], char const ch) noexcept | |||
80 | requires (std::is_array_v<std::remove_cvref_t<decltype(arr)>> && N > 0) { | |||
81 | return implementation::indexOf_impl<std::min(MaxLen, N)>(arr, ch); | |||
82 | } | |||
83 | ||||
84 | template <size_t MaxLen, typename T> | |||
85 | constexpr int32_t indexOfN(T str, char const ch) noexcept | |||
86 | requires (!std::is_array_v<std::remove_cvref_t<T>>) { | |||
87 | return implementation::indexOf_impl<MaxLen>(str, ch); | |||
88 | } | |||
89 | ||||
90 | // Overload for native arrays | |||
91 | template <size_t N> | |||
92 | constexpr int32_t indexOf(char const (&arr)[N], char const ch) noexcept | |||
93 | requires (std::is_array_v<std::remove_cvref_t<decltype(arr)>> && N > 0) { | |||
94 | return implementation::indexOf_impl<N>(arr, ch); | |||
95 | } | |||
96 | ||||
97 | // Searches up to DEFAULT_MAX_EXPECTED_STRING_LENGTH(100) characters in the provided string | |||
98 | // Use indexOfN<> to raise the limit | |||
99 | template <typename T> | |||
100 | 63 | constexpr int32_t indexOf(T str, char const ch) noexcept | ||
101 | requires (!std::is_array_v<std::remove_cvref_t<T>>) { | |||
102 | 63 | return implementation::indexOf_impl<DEFAULT_MAX_EXPECTED_STRING_LENGTH>(str, ch); | ||
103 | } | |||
104 | ||||
105 | namespace tests { | |||
106 | constexpr char const* tstr = "hello"; | |||
107 | static_assert(efiStrlen(tstr) == 5); | |||
108 | static_assert(efiStrlen("hello") == 5); | |||
109 | ||||
110 | constexpr char msg[12] = "world!"; | |||
111 | static_assert(efiStrlen(msg) == 6); | |||
112 | ||||
113 | constexpr char incomplete[12] = {'a', 'b', 'c'}; // not null-terminated | |||
114 | static_assert(efiStrlen(incomplete) == 3); // stops at non-null or 12 | |||
115 | ||||
116 | constexpr char nullstr[12] = {'\0'}; | |||
117 | static_assert(efiStrlen(nullstr) == 0); | |||
118 | ||||
119 | static_assert(indexOf("hello", 'e') == 1); | |||
120 | static_assert(indexOf("hello", 'x') == -1); | |||
121 | ||||
122 | constexpr char name[] = "example"; | |||
123 | static_assert(indexOf(name, 'a') == 2); | |||
124 | ||||
125 | constexpr char const* name2 = "example2S"; | |||
126 | static_assert(indexOf(name2, 'S') == 8); | |||
127 | ||||
128 | constexpr char tst1[] = { ' ', ' ', ' ' }; | |||
129 | static_assert(indexOf(tst1, 'S') == -1); // <- Ok, we knew only 3 were present | |||
130 | static_assert(indexOfN<3>(tst1, 'S') == -1); | |||
131 | static_assert(indexOfN<100>(tst1, 'S') == -1); // Array was passed and size is known OK; | |||
132 | constexpr char const* const tst2 = static_cast<char const*>(tst1); | |||
133 | // SEGFAULT | |||
134 | // static_assert(indexOfN<100>(tst2, 'S') == -1); // Array with few chars and no null-term was passed and size is unknown -> SEGFAULT | |||
135 | // SEGFAULT | |||
136 | static_assert(indexOfN<2>(tst2, 'S') == -1); // <- Ok, we explicitly passed size that is less or equal of real storage size | |||
137 | } | |||
138 | ||||
139 | /* | |||
140 | From https://en.cppreference.com/w/cpp/string/wide/iswspace | |||
141 | Space (0x20, ' ') | |||
142 | Form feed (0x0c, '\f') | |||
143 | Line feed (0x0a, '\n') | |||
144 | Carriage return (0x0d, '\r') | |||
145 | Horizontal tab (0x09, '\t') | |||
146 | Vertical tab (0x0b, '\v'). | |||
147 | */ | |||
148 | 37 | constexpr bool is_whitespace(char const c) { | ||
149 | 37 | constexpr char matches[] = { ' ', '\f', '\n', '\r', '\t', '\v' }; | ||
150 |
1/1✓ Branch 1 taken 37 times.
|
370 | return std::any_of(std::begin(matches), std::end(matches), [c](char c0) { return c == c0; }); | |
151 | } | |||
152 | ||||
153 | 68 | constexpr bool is_digit(char const c) { | ||
154 |
2/4✓ Branch 0 taken 68 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 68 times.
✗ Branch 3 not taken.
|
68 | return c >= '0' && c <= '9'; | |
155 | } | |||
156 | ||||
157 | constexpr bool is_sign(char const c) { | |||
158 | return c == '+' || c == '-'; | |||
159 | } | |||
160 | ||||
161 | // "functor" to check for ws absence in string | |||
162 | struct no_ws { | |||
163 | constexpr explicit no_ws(const char* const str) : data(str) { } | |||
164 | ||||
165 | constexpr explicit operator bool() const { | |||
166 | const std::string_view sv{data}; | |||
167 | return !std::ranges::any_of(sv, is_whitespace); | |||
168 | } | |||
169 | ||||
170 | const char* data; | |||
171 | }; | |||
172 | ||||
173 | constexpr int32_t ATOI_ERROR_CODE = -311223344; | |||
174 | constexpr int32_t ATOI_MAGIC_NUMBER_OF_MAX_PRECEEDING_WHITESPACE_CHARS = 5; | |||
175 | constexpr int32_t ATOI_MAX_INT32_DIGITS = 10; | |||
176 | ||||
177 | namespace tests { | |||
178 | constexpr int32_t ATOI_ERROR_CODE = 10000000; | |||
179 | constexpr int32_t ATOI_ERROR_CODE_BAD_STRING = ATOI_ERROR_CODE + 1; | |||
180 | constexpr int32_t ATOI_ERROR_CODE_TOO_MUCH_WS = ATOI_ERROR_CODE + 2; | |||
181 | constexpr int32_t ATOI_ERROR_CODE_NO_DIGIT_AFTER_SIGN = ATOI_ERROR_CODE + 3; | |||
182 | constexpr int32_t ATOI_ERROR_CODE_NO_DIGITS = ATOI_ERROR_CODE + 4; | |||
183 | constexpr int32_t ATOI_ERROR_CODE_TOO_MUCH_DIGITS = ATOI_ERROR_CODE + 5; | |||
184 | constexpr int32_t ATOI_ERROR_CODE_OVERFLOW_RANK = ATOI_ERROR_CODE + 6; | |||
185 | constexpr int32_t ATOI_ERROR_CODE_OVERFLOW_DIGIT = ATOI_ERROR_CODE + 7; | |||
186 | constexpr int32_t ATOI_ERROR_CODE_NO_DATA = ATOI_ERROR_CODE + 8; | |||
187 | constexpr int32_t ATOI_ERROR_CODE_TRAILING_NON_WS_CHARS = ATOI_ERROR_CODE + 9; | |||
188 | constexpr int32_t ATOI_ERROR_CODE_TRAILING_CHARS = ATOI_ERROR_CODE + 10; | |||
189 | ||||
190 | enum class SafeAtoiErrorMode { | |||
191 | Test, | |||
192 | Prod | |||
193 | }; | |||
194 | } | |||
195 | ||||
196 | enum class SafeAtoiTrailingCharCheckMode { | |||
197 | allowOnlyWhitespace, | |||
198 | allowOnlyNull, | |||
199 | allowAll | |||
200 | }; | |||
201 | ||||
202 | // Mostly based on cppreference atoi page. | |||
203 | // Intended to mimic the behavior of libc's atoi in a constexpr-safe way: | |||
204 | // - Skips leading whitespaces (up to ATOI_MAGIC_NUMBER_OF_MAX_PRECEEDING_WHITESPACE_CHARS) | |||
205 | // - Accepts an optional '+' or '-' sign | |||
206 | // - Parses digits until the first non-digit | |||
207 | // - If onlyAllowWhitespacesAtTheEnd is true, trailing characters must be whitespace or null | |||
208 | // Returns ATOI_ERROR_CODE on invalid input, malformed format, or overflow. | |||
209 | template<tests::SafeAtoiErrorMode errorMode = tests::SafeAtoiErrorMode::Prod, | |||
210 | SafeAtoiTrailingCharCheckMode trailingCharsCheckMode = SafeAtoiTrailingCharCheckMode::allowAll> | |||
211 | 37 | constexpr int32_t safe_atoi(char const* const string) noexcept { | ||
212 |
2/4✓ Branch 0 taken 37 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 37 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 37 times.
|
37 | if (string == nullptr || string[0] == '\0') { |
213 | if constexpr (errorMode == tests::SafeAtoiErrorMode::Test) { | |||
214 | return tests::ATOI_ERROR_CODE_BAD_STRING; | |||
215 | } | |||
216 | ||||
217 | ✗ | return ATOI_ERROR_CODE; | ||
218 | } | |||
219 | ||||
220 | 37 | char const* str{ string }; | ||
221 | ||||
222 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 37 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 37 times.
|
37 | while (is_whitespace(*str)) { |
223 | ✗ | if ((str - string) < ATOI_MAGIC_NUMBER_OF_MAX_PRECEEDING_WHITESPACE_CHARS) { | ||
224 | ✗ | ++str; | ||
225 | } else { | |||
226 | if constexpr (errorMode == tests::SafeAtoiErrorMode::Test) { | |||
227 | return tests::ATOI_ERROR_CODE_TOO_MUCH_WS; | |||
228 | } | |||
229 | ||||
230 | ✗ | return ATOI_ERROR_CODE; | ||
231 | } | |||
232 | } | |||
233 | ||||
234 | 37 | bool const negative{ *str == '-' }; | ||
235 | ||||
236 |
2/4✓ Branch 0 taken 37 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 37 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 37 times.
|
37 | if (*str == '+' || *str == '-') { |
237 | ✗ | ++str; | ||
238 | ||||
239 | ✗ | if (!is_digit(*str)) { | ||
240 | if constexpr (errorMode == tests::SafeAtoiErrorMode::Test) { | |||
241 | return tests::ATOI_ERROR_CODE_NO_DIGIT_AFTER_SIGN; | |||
242 | } | |||
243 | ||||
244 | ✗ | return ATOI_ERROR_CODE; | ||
245 | } | |||
246 | } | |||
247 | ||||
248 | 37 | bool preceding_zero{false}; | ||
249 | ||||
250 | 37 | int32_t digits{}; | ||
251 | ||||
252 | // Skip preceding 0s | |||
253 |
2/2✓ Branch 0 taken 21 times.
✓ Branch 1 taken 37 times.
|
2/2✓ Decision 'true' taken 21 times.
✓ Decision 'false' taken 37 times.
|
58 | while (*str == '0') { |
254 | 21 | ++str; | ||
255 | 21 | digits = 1; | ||
256 | 21 | preceding_zero = true; | ||
257 | } | |||
258 | ||||
259 | 37 | constexpr int32_t MAX_DIV10 = INT32_MIN / 10; // -214748364 | ||
260 | ||||
261 | 37 | int32_t result{}; | ||
262 |
2/2✓ Branch 0 taken 68 times.
✓ Branch 1 taken 37 times.
|
2/2✓ Decision 'true' taken 68 times.
✓ Decision 'false' taken 37 times.
|
105 | while(*str != '\0') { |
263 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 68 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 68 times.
|
68 | if (!is_digit(*str)) { |
264 | // Ok finish here | |||
265 | ✗ | if (digits) { | ||
266 | ✗ | break; | ||
267 | } | |||
268 | ||||
269 | if constexpr (errorMode == tests::SafeAtoiErrorMode::Test) { | |||
270 | return tests::ATOI_ERROR_CODE_NO_DIGITS; | |||
271 | } | |||
272 | ||||
273 | // ill-formed string | |||
274 | ✗ | return ATOI_ERROR_CODE; | ||
275 | } | |||
276 | ||||
277 | // Too many digits for 32-bit int | |||
278 |
3/4✓ Branch 0 taken 3 times.
✓ Branch 1 taken 65 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 68 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 68 times.
|
68 | if (digits >= (ATOI_MAX_INT32_DIGITS + (preceding_zero ? 1 : 0))) { |
279 | if constexpr (errorMode == tests::SafeAtoiErrorMode::Test) { | |||
280 | return tests::ATOI_ERROR_CODE_TOO_MUCH_DIGITS; | |||
281 | } | |||
282 | ||||
283 | ✗ | return ATOI_ERROR_CODE; | ||
284 | } | |||
285 | ||||
286 | 68 | int32_t const digit{ *str - '0' }; | ||
287 | ||||
288 | // Will overflow | |||
289 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 68 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 68 times.
|
68 | if (result < MAX_DIV10) { |
290 | if constexpr (errorMode == tests::SafeAtoiErrorMode::Test) { | |||
291 | return tests::ATOI_ERROR_CODE_OVERFLOW_RANK; | |||
292 | } | |||
293 | ||||
294 | ✗ | return ATOI_ERROR_CODE; | ||
295 | } | |||
296 | ||||
297 | 68 | result *= 10; | ||
298 | ||||
299 | // Will overflow | |||
300 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 68 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 68 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 68 times.
|
68 | if (result < (INT32_MIN + digit + (negative ? 0 : 1))) { |
301 | if constexpr (errorMode == tests::SafeAtoiErrorMode::Test) { | |||
302 | return tests::ATOI_ERROR_CODE_OVERFLOW_DIGIT; | |||
303 | } | |||
304 | ✗ | return ATOI_ERROR_CODE; | ||
305 | } | |||
306 | ||||
307 | 68 | result -= digit; // calculate in negatives to support INT32_MIN | ||
308 | 68 | ++str; | ||
309 | 68 | ++digits; | ||
310 | } | |||
311 | ||||
312 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 37 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 37 times.
|
37 | if (!digits && !preceding_zero) { |
313 | if constexpr (errorMode == tests::SafeAtoiErrorMode::Test) { | |||
314 | return tests::ATOI_ERROR_CODE_NO_DATA; | |||
315 | } | |||
316 | ||||
317 | ✗ | return ATOI_ERROR_CODE; | ||
318 | } | |||
319 | ||||
320 | if constexpr (trailingCharsCheckMode != SafeAtoiTrailingCharCheckMode::allowAll) { | |||
321 | if constexpr (trailingCharsCheckMode == SafeAtoiTrailingCharCheckMode::allowOnlyWhitespace) { | |||
322 | while (*str != '\0' && is_whitespace(*str)) ++str; | |||
323 | if (*str != '\0') { | |||
324 | if constexpr (errorMode == tests::SafeAtoiErrorMode::Test) { | |||
325 | return tests::ATOI_ERROR_CODE_TRAILING_NON_WS_CHARS; | |||
326 | } | |||
327 | ||||
328 | return ATOI_ERROR_CODE; | |||
329 | } | |||
330 | } else if constexpr (trailingCharsCheckMode == SafeAtoiTrailingCharCheckMode::allowOnlyNull) { | |||
331 | if (*str != '\0') { | |||
332 | if constexpr (errorMode == tests::SafeAtoiErrorMode::Test) { | |||
333 | return tests::ATOI_ERROR_CODE_TRAILING_CHARS; | |||
334 | } | |||
335 | ||||
336 | return ATOI_ERROR_CODE; | |||
337 | } | |||
338 | } | |||
339 | } | |||
340 | ||||
341 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 37 times.
|
37 | return negative ? result : -result; | |
342 | } | |||
343 | ||||
344 | namespace tests { | |||
345 | static_assert(safe_atoi<SafeAtoiErrorMode::Test, SafeAtoiTrailingCharCheckMode::allowOnlyNull>(nullptr) == ATOI_ERROR_CODE_BAD_STRING); | |||
346 | static_assert(safe_atoi<SafeAtoiErrorMode::Test, SafeAtoiTrailingCharCheckMode::allowOnlyNull>("") == ATOI_ERROR_CODE_BAD_STRING); | |||
347 | static_assert(safe_atoi<SafeAtoiErrorMode::Test, SafeAtoiTrailingCharCheckMode::allowOnlyNull>("a123") == ATOI_ERROR_CODE_NO_DIGITS); | |||
348 | static_assert(safe_atoi<SafeAtoiErrorMode::Test, SafeAtoiTrailingCharCheckMode::allowOnlyNull>("-a123") == ATOI_ERROR_CODE_NO_DIGIT_AFTER_SIGN); | |||
349 | static_assert(safe_atoi<SafeAtoiErrorMode::Test, SafeAtoiTrailingCharCheckMode::allowOnlyNull>("- 123") == ATOI_ERROR_CODE_NO_DIGIT_AFTER_SIGN); | |||
350 | static_assert(safe_atoi<SafeAtoiErrorMode::Test, SafeAtoiTrailingCharCheckMode::allowOnlyNull>(" + 123") == ATOI_ERROR_CODE_NO_DIGIT_AFTER_SIGN); | |||
351 | static_assert(safe_atoi<SafeAtoiErrorMode::Prod, SafeAtoiTrailingCharCheckMode::allowOnlyNull>("1234567") == 1234567); | |||
352 | static_assert(safe_atoi<SafeAtoiErrorMode::Prod, SafeAtoiTrailingCharCheckMode::allowOnlyNull>(" -1234567890") == -1234567890); | |||
353 | static_assert(safe_atoi<SafeAtoiErrorMode::Prod, SafeAtoiTrailingCharCheckMode::allowOnlyNull>(" +1234567890") == 1234567890); | |||
354 | static_assert(safe_atoi<SafeAtoiErrorMode::Test, SafeAtoiTrailingCharCheckMode::allowOnlyNull>(" -1234567890 ") == ATOI_ERROR_CODE_TRAILING_CHARS); | |||
355 | static_assert(safe_atoi<SafeAtoiErrorMode::Prod, SafeAtoiTrailingCharCheckMode::allowOnlyWhitespace>(" -1234567890 ") == -1234567890); | |||
356 | static_assert(safe_atoi<SafeAtoiErrorMode::Test>("+ 42") == ATOI_ERROR_CODE_NO_DIGIT_AFTER_SIGN); | |||
357 | static_assert(safe_atoi<SafeAtoiErrorMode::Test, SafeAtoiTrailingCharCheckMode::allowAll>(" ") == ATOI_ERROR_CODE_TOO_MUCH_WS); | |||
358 | ||||
359 | // Too many leading whitespaces | |||
360 | static_assert(safe_atoi<SafeAtoiErrorMode::Test>(" 123") == ATOI_ERROR_CODE_TOO_MUCH_WS); | |||
361 | ||||
362 | // No digits after just whitespaces | |||
363 | static_assert(safe_atoi<SafeAtoiErrorMode::Test>(" ") == ATOI_ERROR_CODE_NO_DATA); | |||
364 | ||||
365 | // Only a sign, no digits | |||
366 | static_assert(safe_atoi<SafeAtoiErrorMode::Test>("+") == ATOI_ERROR_CODE_NO_DIGIT_AFTER_SIGN); | |||
367 | static_assert(safe_atoi<SafeAtoiErrorMode::Test>("-") == ATOI_ERROR_CODE_NO_DIGIT_AFTER_SIGN); | |||
368 | ||||
369 | // Too many digits (11) | |||
370 | static_assert(safe_atoi<SafeAtoiErrorMode::Test>("12345678901") == ATOI_ERROR_CODE_TOO_MUCH_DIGITS); | |||
371 | static_assert(safe_atoi<SafeAtoiErrorMode::Test>("111111111111111") == ATOI_ERROR_CODE_TOO_MUCH_DIGITS); | |||
372 | ||||
373 | // INT32_MAX is not representable in negative space (this will overflow) | |||
374 | static_assert(safe_atoi<SafeAtoiErrorMode::Prod>("2147483647") == 2147483647); | |||
375 | static_assert(safe_atoi<SafeAtoiErrorMode::Prod>("00000000000000000002147483647") == 2147483647); | |||
376 | static_assert(safe_atoi<SafeAtoiErrorMode::Prod>("+00000000000000000002147483647") == 2147483647); | |||
377 | static_assert(safe_atoi<SafeAtoiErrorMode::Test>("2147483648") == ATOI_ERROR_CODE_OVERFLOW_DIGIT); | |||
378 | static_assert(safe_atoi<SafeAtoiErrorMode::Test>("2147483657") == ATOI_ERROR_CODE_OVERFLOW_RANK); | |||
379 | static_assert(safe_atoi<SafeAtoiErrorMode::Test>("99999999999999999") == ATOI_ERROR_CODE_OVERFLOW_RANK); | |||
380 | ||||
381 | static_assert(safe_atoi<SafeAtoiErrorMode::Prod>("-2147483648") == -2147483648); | |||
382 | static_assert(safe_atoi<SafeAtoiErrorMode::Prod>("-00000000000000000002147483648") == -2147483648); | |||
383 | static_assert(safe_atoi<SafeAtoiErrorMode::Test>("-2147483649") == ATOI_ERROR_CODE_OVERFLOW_DIGIT); | |||
384 | static_assert(safe_atoi<SafeAtoiErrorMode::Test>("-2147483658") == ATOI_ERROR_CODE_OVERFLOW_RANK); | |||
385 | static_assert(safe_atoi<SafeAtoiErrorMode::Test>("-99999999999999999") == ATOI_ERROR_CODE_OVERFLOW_RANK); | |||
386 | ||||
387 | // Accept +0 and -0 | |||
388 | static_assert(safe_atoi<SafeAtoiErrorMode::Prod>("+0") == 0); | |||
389 | static_assert(safe_atoi<SafeAtoiErrorMode::Prod>("-0") == 0); | |||
390 | static_assert(safe_atoi<SafeAtoiErrorMode::Prod>("0") == 0); | |||
391 | static_assert(safe_atoi<SafeAtoiErrorMode::Prod>(" 0 ") == 0); | |||
392 | static_assert(safe_atoi<SafeAtoiErrorMode::Prod>(" 0") == 0); | |||
393 | static_assert(safe_atoi<SafeAtoiErrorMode::Prod>("0 ") == 0); | |||
394 | ||||
395 | static_assert(safe_atoi<SafeAtoiErrorMode::Test, SafeAtoiTrailingCharCheckMode::allowOnlyWhitespace>("123abc") == ATOI_ERROR_CODE_TRAILING_NON_WS_CHARS); | |||
396 | static_assert(safe_atoi<SafeAtoiErrorMode::Test, SafeAtoiTrailingCharCheckMode::allowOnlyNull>("123abc") == ATOI_ERROR_CODE_TRAILING_CHARS); | |||
397 | static_assert(safe_atoi<SafeAtoiErrorMode::Prod, SafeAtoiTrailingCharCheckMode::allowAll>("123abc") == 123); | |||
398 | ||||
399 | // Max allowed whitespace (5) - should succeed | |||
400 | static_assert(safe_atoi<SafeAtoiErrorMode::Prod>(" 42") == 42); | |||
401 | // 6 ws - fail | |||
402 | static_assert(safe_atoi<SafeAtoiErrorMode::Prod>(" 42") == stringutil::ATOI_ERROR_CODE); | |||
403 | } | |||
404 | ||||
405 | } | |||
406 |