OXYGENHUB
◆ main openmsx / src / utils / StringOp.hh
9.0 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#ifndef STRINGOP_HH
2
#define STRINGOP_HH
3
4
#include "IterableBitSet.hh"
5
#include "stringsp.hh"
6
7
#include <algorithm>
8
#include <charconv>
9
#include <concepts>
10
#include <cstdint>
11
#include <cstdlib>
12
#include <cstring>
13
#include <limits>
14
#include <optional>
15
#include <string>
16
#include <string_view>
17
#include <type_traits>
18
#include <utility>
19
20
#ifdef __APPLE__
21
// Only forward declare 'CFStringRef' (for fromCFString() below). Including
22
// <CoreFoundation/CoreFoundation.h> here would drag it into the ~75% of all
23
// translation units that (indirectly) include this header, and it declares a
24
// lot of names in the global namespace. E.g. MacTypes.h declares a global
25
// 'FixedPoint', which clashes with openmsx::FixedPoint.
26
using CFStringRef = const struct __CFString*;
27
#endif
28
29
namespace StringOp
30
{
31
	/** Convert a string to an integral type 'T' (int, uint64_t, ...).
32
	  * This is similar to, but not quite the same as the family of
33
	  * 'strtoll()' functions:
34
	  * - Leading whitespace is NOT accepted (unlike strtoll()).
35
	  * - There may NOT be any trailing character after the value (unlike
36
	  *   strtoll()).
37
	  * - (Only) if 'T' is a signed type, the value may start with a '-'
38
	  *   character.
39
	  * - A leading '+' character is NOT accepted.
40
	  * - It's an error if the value cannot be represented by the type 'T'.
41
	  * - This function tries to detect the base of the value by looking at
42
	  *   the prefix. (Only) these prefixes are accepted:
43
	  *    - '0x' or '0X': hexadecimal
44
	  *    - '0b' or '0B': binary
45
	  *    - no prefix: decimal
46
	  *   Note that prefix '0' for octal is NOT supported.
47
	  * - The input is a 'string_view' (rather than a 'const char*'), so it
48
	  *   is not required to be zero-terminated.
49
	  */
50
	template<std::integral T> [[nodiscard]] std::optional<T> stringTo(std::string_view s);
51
52
	/** As above, but without dynamic base detection. Moreover leading
53
	  * prefixes like '0x' for hexadecimal are seen as invalid input.
54
	  */
55
	template<int BASE, std::integral T> [[nodiscard]] std::optional<T> stringToBase(std::string_view s);
56
57
	[[nodiscard]] bool stringToBool(std::string_view str);
58
59
	[[nodiscard]] std::string toLower(std::string_view str);
60
61
	void trimRight(std::string& str, const char* chars);
62
	void trimRight(std::string& str, char chars);
63
	void trimRight(std::string_view& str, std::string_view chars);
64
	void trimRight(std::string_view& str, char chars);
65
	void trimLeft (std::string& str, const char* chars);
66
	void trimLeft (std::string& str, char chars);
67
	void trimLeft (std::string_view& str, std::string_view chars);
68
	void trimLeft (std::string_view& str, char chars);
69
	void trim     (std::string_view& str, std::string_view chars);
70
	void trim     (std::string_view& str, char chars);
71
72
	[[nodiscard]] std::pair<std::string_view, std::string_view> splitOnFirst(
73
		std::string_view str, std::string_view chars);
74
	[[nodiscard]] std::pair<std::string_view, std::string_view> splitOnFirst(
75
		std::string_view str, char chars);
76
	[[nodiscard]] std::pair<std::string_view, std::string_view> splitOnLast(
77
		std::string_view str, std::string_view chars);
78
	[[nodiscard]] std::pair<std::string_view, std::string_view> splitOnLast(
79
		std::string_view str, char chars);
80
	[[nodiscard]] IterableBitSet<64> parseRange(std::string_view str,
81
	                                            unsigned min, unsigned max);
82
83
	//[[nodiscard]] std::vector<std::string_view> split(std::string_view str, char chars);
84
85
	enum class EmptyParts : uint8_t {
86
		KEEP,  // "a,b,,c" -> "a", "b", "", "c"
87
		REMOVE // "a,b,,c" -> "a", "b", "c"       BUT  ",,a,b" -> "", "a", "b"  (keeps one empty part in front)
88
	};
89
	template<EmptyParts keepOrRemove = EmptyParts::KEEP, typename Separators>
90
	[[nodiscard]] inline auto split_view(std::string_view str, Separators separators) {
91
		struct Iterator {
92
			using value_type      = std::string_view;
93
			using difference_type = ptrdiff_t;
94
95
			Iterator() = default;
96
			Iterator(const Iterator&) = default;
97
			Iterator& operator=(const Iterator&) = default;
98
99
			Iterator(std::string_view str_, Separators separators_)
100
				: str(str_), separators(separators_) {
101
				str.remove_prefix(skipSeparators(0));
102
				next_p();
103
			}
104
105
			[[nodiscard]] value_type operator*() const {
106
				difference_type dummy; (void)dummy; // avoid warning about unused typedef
107
				return {str.data(), p};
108
			}
109
110
			Iterator& operator++() {
111
				if (p < str.size()) {
112
					str.remove_prefix(skipSeparators(p + 1));
113
					next_p();
114
				} else {
115
					str = "";
116
				}
117
				return *this;
118
			}
119
			Iterator operator++(int) { auto copy = *this; ++(*this); return copy; }
120
121
			[[nodiscard]] bool operator==(const Iterator&) const = default;
122
			[[nodiscard]] bool operator==(std::default_sentinel_t) const { return str.empty(); }
123
124
		private:
125
			static bool isSeparator(char c, char separators) {
126
				return c == separators;
127
			}
128
			static bool isSeparator(char c, std::string_view separators) {
129
				return separators.contains(c);
130
			}
131
132
			void next_p() {
133
				p = 0;
134
				while ((p < str.size()) && !isSeparator(str[p], separators)) ++p;
135
			}
136
137
			[[nodiscard]] std::string_view::size_type skipSeparators(std::string_view::size_type pos) const {
138
				if (keepOrRemove == EmptyParts::REMOVE) {
139
					while ((pos < str.size()) && isSeparator(str[pos], separators)) ++pos;
140
				}
141
				return pos;
142
			}
143
144
		private:
145
			std::string_view str;
146
			std::string_view::size_type p;
147
			Separators separators;
148
		};
149
		static_assert(std::forward_iterator<Iterator>);
150
		static_assert(std::sentinel_for<std::default_sentinel_t, Iterator>);
151
152
		struct Splitter {
153
			std::string_view str;
154
			Separators separators;
155
156
			[[nodiscard]] auto begin() const { return Iterator{str, separators}; }
157
			[[nodiscard]] auto end()   const { return std::default_sentinel_t{}; }
158
		};
159
160
		return Splitter{str, separators};
161
	}
162
163
	// case insensitive less-than operator
164
	struct caseless {
165
		[[nodiscard]] bool operator()(std::string_view s1, std::string_view s2) const {
166
			auto m = std::min(s1.size(), s2.size());
167
			int r = strncasecmp(s1.data(), s2.data(), m);
168
			return (r != 0) ? (r < 0) : (s1.size() < s2.size());
169
		}
170
	};
171
	// case insensitive greater-than operator
172
	struct inv_caseless {
173
		[[nodiscard]] bool operator()(std::string_view s1, std::string_view s2) const {
174
			auto m = std::min(s1.size(), s2.size());
175
			int r = strncasecmp(s1.data(), s2.data(), m);
176
			return (r != 0) ? (r > 0) : (s1.size() > s2.size());
177
		}
178
	};
179
	struct casecmp {
180
		[[nodiscard]] bool operator()(std::string_view s1, std::string_view s2) const {
181
			if (s1.size() != s2.size()) return false;
182
			return strncasecmp(s1.data(), s2.data(), s1.size()) == 0;
183
		}
184
	};
185
186
	[[nodiscard]] inline bool containsCaseInsensitive(std::string_view haystack, std::string_view needle)
187
	{
188
		return !std::ranges::search(haystack, needle,
189
		                   [](char x, char y) { return toupper(x) == toupper(y); }
190
		        ).empty();
191
	}
192
193
#ifdef __APPLE__
194
	[[nodiscard]] std::string fromCFString(CFStringRef str);
195
#endif
196
197
	template<int BASE, std::integral T>
198
	[[nodiscard]] std::optional<T> stringToBase(std::string_view s)
199
	{
200
		T result = {}; // dummy init to avoid warning
201
		const auto* b = s.data();
202
		const auto* e = s.data() + s.size();
203
		if (auto [p, ec] = std::from_chars(b, e, result, BASE);
204
		    (ec == std::errc()) && (p == e)) {
205
			return result;
206
		}
207
		return std::nullopt;
208
	}
209
210
	template<std::integral T>
211
	[[nodiscard]] std::optional<T> stringTo(std::string_view s)
212
	{
213
		if (s.empty()) [[unlikely]] return {};
214
		if constexpr (std::is_signed_v<T>) {
215
			bool negate = false;
216
			if (s[0] == '-') [[unlikely]] {
217
				negate = true;
218
				s.remove_prefix(1);
219
			}
220
221
			using U = std::make_unsigned_t<T>;
222
			auto tmp = stringTo<U>(s);
223
			if (!tmp) [[unlikely]] return {};
224
225
			U max = U(std::numeric_limits<T>::max()) + 1; // 0x8000
226
			if (negate) [[unlikely]] {
227
				if (*tmp > max) [[unlikely]] return {}; // 0x8000
228
				return T(~*tmp + 1);
229
			} else {
230
				if (*tmp >= max) [[unlikely]] return {}; // 0x7fff
231
				return T(*tmp);
232
			}
233
		} else {
234
			if (s[0] != '0') [[likely]] {
235
				return stringToBase<10, T>(s);
236
			} else {
237
				if (s.size() == 1) return T(0);
238
				if ((s[1] == 'x') || (s[1] == 'X')) [[likely]] {
239
					s.remove_prefix(2);
240
					return stringToBase<16, T>(s);
241
				} else if ((s[1] == 'b') || (s[1] == 'B')) {
242
					s.remove_prefix(2);
243
					return stringToBase<2, T>(s);
244
				} else {
245
					return stringToBase<10, T>(s);
246
				}
247
			}
248
		}
249
	}
250
251
	#if 0
252
	// Doesn't work yet in MacOS :(
253
	template<std::floating_point T>
254
	[[nodiscard]] std::optional<T> stringTo(std::string_view s)
255
	{
256
		T result{}; // dummy init
257
		const char* b = s.data();
258
		const char* e = s.data() + s.size();
259
		if (auto [p, ec] = std::from_chars(b, e, result);
260
		    (ec == std::errc{}) && (p == e)) {
261
			return result;
262
		}
263
		return std::nullopt;
264
	}
265
	#else
266
	template<typename T>
267
		requires std::is_same_v<T, double>
268
	[[nodiscard]] std::optional<T> stringTo(std::string_view s)
269
	{
270
		// strtod expects a null-terminated string
271
		char buf[128];
272
		if (s.size() >= sizeof(buf)) return std::nullopt;
273
		memcpy(buf, s.data(), s.size());
274
		buf[s.size()] = '\0';
275
276
		errno = 0;
277
		char* end = nullptr;
278
		double value = strtod(buf, &end);
279
280
		if (errno != 0) return std::nullopt;
281
		if (end != (buf + s.size())) return std::nullopt;
282
		return value;
283
	}
284
	#endif
285
}
286
287
#endif