1// SPDX-License-Identifier: GPL-3.0-or-later
2#pragma once
3
4#include <cstddef>
5#include <mos/default_allocator.hpp>
6#include <mos/string_view.hpp>
7#include <mos_stdlib.hpp>
8#include <mos_string.hpp>
9
10namespace mos
11{
12 constexpr size_t short_string_capacity = 16;
13
14 template<typename Char, typename TAllocator = mos::default_allocator<Char>>
15 class basic_string
16 {
17 bool _is_long = false;
18 size_t _length; ///< string length EXcluding null terminator
19
20 union
21 {
22 struct _long_buffer
23 {
24 Char *_buffer;
25 size_t _capacity; ///< buffer capacity includes null terminator
26 } _long;
27
28 struct _short_buffer
29 {
30 Char _buffer[short_string_capacity];
31 } _short;
32 } _data;
33
34 public:
35 static constexpr auto npos = size_t(-1);
36
37 public:
38 basic_string(std::nullptr_t) = delete;
39
40 basic_string() : _length{ 0 }, _is_long(false) {};
41
42 basic_string(const Char *buffer)
43 {
44 _init(buffer, size: generic_strlen(buffer));
45 }
46
47 explicit basic_string(const Char *buffer, size_t size)
48 {
49 _init(buffer, size);
50 }
51
52 template<class StringViewLike>
53 explicit basic_string(const StringViewLike &view)
54 {
55 _init(buffer: view.data(), size: view.size());
56 }
57
58 basic_string(size_t size, Char c = 0)
59 {
60 _init(buffer: &c, size);
61 }
62
63 basic_string(const basic_string &other)
64 {
65 _init(buffer: other.data(), size: other.size());
66 }
67
68 basic_string(basic_string &&other)
69 {
70 _length = other._length;
71 _is_long = other._is_long;
72 if (_is_long)
73 {
74 _data._long._capacity = other._data._long._capacity;
75 _data._long._buffer = other._data._long._buffer;
76 other._data._long._buffer = nullptr; // detach
77 }
78 else
79 {
80 for (size_t i = 0; i < short_string_capacity; i++)
81 _data._short._buffer[i] = other._data._short._buffer[i];
82 }
83 }
84
85 ~basic_string()
86 {
87 if (_is_long && _data._long._buffer)
88 TAllocator::free(_data._long._buffer);
89 }
90
91 basic_string &operator=(const Char *buffer)
92 {
93 if (buffer == nullptr)
94 mos::__raise_null_pointer_exception();
95 this->~basic_string();
96 this->_length = generic_strlen(buffer);
97 this->_is_long = this->_length >= short_string_capacity;
98 if (this->_is_long)
99 {
100 this->_data._long._capacity = this->_length + 1;
101 this->_data._long._buffer = (Char *) TAllocator::allocate(sizeof(Char) * this->_data._long._capacity);
102 memcpy(this->_data._long._buffer, buffer, sizeof(Char) * this->_length);
103 this->_data._long._buffer[this->_length] = 0;
104 }
105 else
106 {
107 for (size_t i = 0; i < short_string_capacity; i++)
108 this->_data._short._buffer[i] = buffer[i];
109 }
110 return *this;
111 }
112
113 basic_string &operator=(const mos::basic_string_view<Char> &view)
114 {
115 this->~basic_string();
116 this->_length = view.size();
117 this->_is_long = this->_length >= short_string_capacity;
118 if (this->_is_long)
119 {
120 this->_data._long._capacity = this->_length + 1;
121 this->_data._long._buffer = (Char *) TAllocator::allocate(sizeof(Char) * this->_data._long._capacity);
122 memcpy(this->_data._long._buffer, view.data(), sizeof(Char) * this->_length);
123 this->_data._long._buffer[this->_length] = 0;
124 }
125 else
126 {
127 for (size_t i = 0; i < short_string_capacity; i++)
128 this->_data._short._buffer[i] = view.data()[i];
129 }
130 return *this;
131 }
132
133 basic_string &operator=(basic_string &&other)
134 {
135 this->~basic_string();
136 this->_length = other._length;
137 this->_is_long = other._is_long;
138 if (this->_is_long)
139 {
140 this->_data._long._capacity = other._data._long._capacity;
141 this->_data._long._buffer = other._data._long._buffer;
142 other._data._long._buffer = nullptr; // detach
143 }
144 else
145 {
146 for (size_t i = 0; i < short_string_capacity; i++)
147 this->_data._short._buffer[i] = other._data._short._buffer[i];
148 }
149 return *this;
150 }
151
152 basic_string &operator=(const basic_string &other)
153 {
154 _init(buffer: other.data(), size: other.size());
155 return *this;
156 }
157
158 friend basic_string operator+(const basic_string_view<Char> &lhs, const basic_string &rhs)
159 {
160 return basic_string(lhs) + rhs;
161 }
162
163 void resize(size_t new_length)
164 {
165 if (new_length == _length)
166 return;
167 if (new_length < short_string_capacity)
168 _convert_to_short();
169 else
170 _convert_to_long(new_length: _length);
171 _length = new_length;
172 }
173
174 bool begins_with(Char c) const
175 {
176 if (_length == 0)
177 return false;
178 return _data._short._buffer[0] == c;
179 }
180
181 bool begins_with(const basic_string_view<Char> &prefix) const
182 {
183 if (prefix.size() > _length)
184 return false;
185 return generic_strncmp(data(), prefix.data(), prefix.size()) == 0;
186 }
187
188 bool ends_with(Char c) const
189 {
190 if (_length == 0)
191 return false;
192 return _data._short._buffer[_length - 1] == c;
193 }
194
195 bool ends_with(const basic_string_view<Char> &suffix) const
196 {
197 if (suffix.size() > _length)
198 return false;
199 return generic_strncmp(data() + _length - suffix.size(), suffix.data(), suffix.size()) == 0;
200 }
201
202 void clear()
203 {
204 if (_is_long)
205 {
206 TAllocator::free(_data._long._buffer);
207 _data._long._buffer = nullptr;
208 _is_long = false;
209 }
210 _length = 0;
211 }
212
213 basic_string operator+(const basic_string &other) const
214 {
215 auto copy = *this;
216 return copy += other;
217 }
218
219 basic_string operator+(Char c) const
220 {
221 return operator+(basic_string_view(&c, 1));
222 }
223
224 void push_back(Char c)
225 {
226 operator+=(c);
227 }
228
229 basic_string &operator+=(const basic_string_view<Char> &other)
230 {
231 const auto new_length = _length + other.size();
232 if (new_length < short_string_capacity)
233 {
234 memcpy(_data._short._buffer + _length, other.data(), sizeof(Char) * other.size());
235 _data._short._buffer[new_length] = 0;
236 _length = new_length;
237 }
238 else
239 {
240 _convert_to_long(new_length);
241 memcpy(_data._long._buffer + _length, other.data(), sizeof(Char) * other.size());
242 _data._long._buffer[new_length] = 0;
243 _length = new_length;
244 }
245
246 return *this;
247 }
248
249 basic_string &operator+=(Char c)
250 {
251 return operator+=(basic_string_view(&c, 1));
252 }
253
254 const Char *data() const
255 {
256 return _is_long ? _data._long._buffer : _data._short._buffer;
257 }
258
259 const Char *c_str() const
260 {
261 return data();
262 }
263
264 Char &operator[](size_t index)
265 {
266 return _is_long ? _data._long._buffer[index] : _data._short._buffer[index];
267 }
268
269 const Char &operator[](size_t index) const
270 {
271 return _is_long ? _data._long._buffer[index] : _data._short._buffer[index];
272 }
273
274 size_t size() const
275 {
276 return _length;
277 }
278
279 bool empty() const
280 {
281 return _length == 0;
282 }
283
284 ssize_t copy(Char *buffer, size_t size, size_t pos = 0) const
285 {
286 if (pos > _length)
287 return -1;
288 if (pos + size > _length)
289 size = _length - pos;
290 memcpy(buffer, data() + pos, sizeof(Char) * size);
291 return size;
292 }
293
294 basic_string_view<Char> value_or(basic_string_view<Char> other) const
295 {
296 return empty() ? other : *this;
297 }
298
299 Char *begin()
300 {
301 return _is_long ? &_data._long._buffer[0] : &_data._short._buffer[0];
302 }
303 const Char *begin() const
304 {
305 return _is_long ? &_data._long._buffer[0] : &_data._short._buffer[0];
306 }
307
308 Char *end()
309 {
310 return _is_long ? &_data._long._buffer[_length] : &_data._short._buffer[_length];
311 }
312 const Char *end() const
313 {
314 return _is_long ? &_data._long._buffer[_length] : &_data._short._buffer[_length];
315 }
316
317 bool operator==(const basic_string &other) const
318 {
319 return _do_compare(other: other.data(), other_size: other.size()) == 0;
320 }
321
322 bool operator==(const char *rhs) const
323 {
324 return _do_compare(other: rhs, other_size: generic_strlen(c: rhs)) == 0;
325 }
326
327 operator basic_string_view<Char>() const
328 {
329 return basic_string_view<Char>(data(), _length);
330 }
331
332 bool operator==(std::nullptr_t) const = delete;
333
334 private:
335 void _init(const Char *buffer, size_t size)
336 {
337 if (buffer == nullptr)
338 mos::__raise_null_pointer_exception();
339 _length = size;
340 if (_length < short_string_capacity)
341 {
342 for (size_t i = 0; i < _length; i++)
343 _data._short._buffer[i] = buffer[i];
344 _data._short._buffer[_length] = 0;
345 _is_long = false;
346 }
347 else
348 {
349 _data._long._capacity = sizeof(Char) * _length + 1;
350 _data._long._buffer = (Char *) TAllocator::allocate(_data._long._capacity);
351 memcpy(_data._long._buffer, buffer, sizeof(Char) * size);
352 _data._long._buffer[size] = 0;
353 _is_long = true;
354 }
355 }
356 int _do_compare(const Char *other, const size_t other_size) const
357 {
358 if (_length != other_size)
359 return _length < other_size ? -1 : 1;
360
361 return generic_strncmp(data(), other, _length);
362 }
363
364 void _convert_to_long(size_t new_length = 0)
365 {
366 if (_is_long)
367 return;
368
369 const auto new_capacity = sizeof(Char) * std::max(a: new_length, b: _length) + 1; //_length + 1 for null terminator
370 const auto new_buffer = (Char *) TAllocator::allocate(new_capacity);
371 memcpy(new_buffer, _data._short._buffer, std::min(a: short_string_capacity, b: _length) * sizeof(Char));
372 new_buffer[_length] = 0;
373
374 _data._long._buffer = new_buffer;
375 _data._long._capacity = new_capacity;
376 _is_long = true;
377 }
378
379 void _convert_to_short()
380 {
381 if (!_is_long)
382 return;
383
384 const auto buffer = _data._long._buffer;
385 const auto capacity = _data._long._capacity;
386
387 memcpy(_data._short._buffer, buffer, std::min(a: short_string_capacity, b: _length) * sizeof(Char));
388 _data._short._buffer[_length] = 0;
389
390 TAllocator::free(buffer);
391 _is_long = false;
392 }
393 };
394
395 using string = mos::basic_string<char>;
396
397 // convert a pointer to a string
398 mos::string to_string(const void *value);
399} // namespace mos
400