1/*
2 * LegacyClonk
3 *
4 * Copyright (c) RedWolf Design
5 * Copyright (c) 2017-2021, The LegacyClonk Team and contributors
6 *
7 * Distributed under the terms of the ISC license; see accompanying file
8 * "COPYING" for details.
9 *
10 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
11 * See accompanying file "TRADEMARK" for details.
12 *
13 * To redistribute this file separately, substitute the full license texts
14 * for the above references.
15 */
16
17// Standard buffer classes
18
19#pragma once
20
21#include "Standard.h"
22
23#include <stdlib.h>
24#include <assert.h>
25#include <stdarg.h>
26
27#include <concepts>
28#include <cstring>
29#include <utility>
30#include <type_traits>
31
32// Base buffer class. Either references or holds data.
33class StdBuf
34{
35public:
36 // *** Construction
37
38 StdBuf() : fRef(true), pData(nullptr), iSize(0) {}
39
40 // Constructor from other buffer (copy construction)
41 // Set by constant data. Copies data if not specified otherwise.
42 StdBuf(const void *pData, size_t iSize, bool fCopy = true)
43 : fRef(true), pData(pData), iSize(iSize)
44 {
45 if (fCopy) Copy();
46 }
47
48 StdBuf(const StdBuf &Buf2, bool fCopy = true)
49 : fRef(true), pData(nullptr), iSize(0)
50 {
51 if (fCopy)
52 {
53 Copy(Buf2);
54 }
55 else
56 {
57 Ref(Buf2);
58 }
59 }
60
61 StdBuf(StdBuf &&Buf2, bool fCopy = false)
62 : fRef(true), pData(nullptr), iSize(0)
63 {
64 if (fCopy)
65 {
66 Copy(Buf2);
67 }
68 else if (!Buf2.isRef())
69 {
70 Take(Buf2: std::forward<StdBuf>(t&: Buf2));
71 }
72 else
73 {
74 Ref(Buf2);
75 }
76 }
77
78 ~StdBuf()
79 {
80 Clear();
81 }
82
83 static StdBuf MakeRef(const void *pData, size_t iSize)
84 {
85 return StdBuf(pData, iSize, false);
86 }
87
88 static StdBuf TakeOrRef(StdBuf &other)
89 {
90 StdBuf ret;
91 if (other.isRef()) ret.Ref(Buf2: other);
92 else ret.Take(Buf2&: other);
93 return ret;
94 }
95
96protected:
97 // Reference? Otherwise, this object holds the data.
98 bool fRef;
99 // Data
100 union
101 {
102 const void *pData;
103 void *pMData;
104#ifndef NDEBUG
105 char *szString; // for debugger preview
106#endif
107 };
108 size_t iSize;
109
110public:
111 // *** Getters
112
113 bool isNull() const { return !getData(); }
114 const void *getData() const { return fRef ? pData : pMData; }
115 void *getMData() { assert(!fRef); return pMData; }
116 size_t getSize() const { return iSize; }
117 bool isRef() const { return fRef; }
118
119 const void *getPtr(size_t i) const { return reinterpret_cast<const char *>(getData()) + i; }
120 void *getMPtr(size_t i) { return reinterpret_cast<char *>(getMData()) + i; }
121
122 StdBuf getPart(size_t iStart, size_t inSize) const
123 {
124 assert(iStart + inSize <= iSize);
125 return StdBuf(getPtr(i: iStart), inSize, false);
126 }
127
128 // *** Setters
129
130 // * Direct setters
131
132 // Reference given data
133 void Ref(const void *pnData, size_t inSize)
134 {
135 Clear();
136 fRef = true; pData = pnData; iSize = inSize;
137 }
138
139 // Take over data (hold it)
140 void Take(void *pnData, size_t inSize)
141 {
142 Clear();
143 if (pnData)
144 {
145 fRef = false; pMData = pnData; iSize = inSize;
146 }
147 }
148
149 // Transfer puffer ownership to the caller
150 void *GrabPointer()
151 {
152 if (isNull()) return nullptr;
153 // Do not give out a buffer which someone else will free
154 if (fRef) Copy();
155 void *pMData = getMData();
156 pData = pMData; fRef = true;
157 return pMData;
158 }
159
160 // * Buffer data operations
161
162 // Create new buffer with given size
163 void New(size_t inSize)
164 {
165 Clear();
166 pMData = malloc(size: iSize = inSize);
167 fRef = false;
168 }
169
170 // Write data into the buffer
171 void Write(const void *pnData, size_t inSize, size_t iAt = 0)
172 {
173 assert(iAt + inSize <= iSize);
174 if (pnData && inSize) memcpy(dest: getMPtr(i: iAt), src: pnData, n: inSize);
175 }
176
177 // Move data around inside the buffer (checks overlap)
178 void Move(size_t iFrom, size_t inSize, size_t iTo = 0)
179 {
180 assert(iFrom + inSize <= iSize); assert(iTo + inSize <= iSize);
181 memmove(dest: getMPtr(i: iTo), src: getPtr(i: iFrom), n: inSize);
182 }
183
184 // Compare to memory
185 int Compare(const void *pCData, size_t iCSize, size_t iAt = 0) const
186 {
187 assert(iAt + iCSize <= getSize());
188 return memcmp(s1: getPtr(i: iAt), s2: pCData, n: iCSize);
189 }
190
191 // Grow the buffer
192 void Grow(size_t iGrow)
193 {
194 // Grow dereferences
195 if (fRef) { Copy(inSize: iSize + iGrow); return; }
196 if (!iGrow) return;
197 // Realloc
198 pMData = realloc(ptr: pMData, size: iSize += iGrow);
199 }
200
201 // Shrink the buffer
202 void Shrink(size_t iShrink)
203 {
204 assert(iSize >= iShrink);
205 // Shrink dereferences
206 if (fRef) { Copy(inSize: iSize - iShrink); return; }
207 if (!iShrink) return;
208 // Realloc
209 pMData = realloc(ptr: pMData, size: iSize -= iShrink);
210 }
211
212 // Clear buffer
213 void Clear()
214 {
215 if (!fRef) free(ptr: pMData);
216 pMData = nullptr; fRef = true; iSize = 0;
217 }
218
219 // Free buffer that had been grabbed
220 static void DeletePointer(void *data)
221 {
222 free(ptr: data);
223 }
224
225 // * Composed actions
226
227 // Set buffer size (dereferences)
228 void SetSize(size_t inSize)
229 {
230 if (inSize > iSize)
231 Grow(iGrow: inSize - iSize);
232 else
233 Shrink(iShrink: iSize - inSize);
234 }
235
236 // Write buffer contents into the buffer
237 void Write(const StdBuf &Buf2, size_t iAt = 0)
238 {
239 Write(pnData: Buf2.getData(), inSize: Buf2.getSize(), iAt);
240 }
241
242 // Compare (a part of) this buffer's contents to another's
243 int Compare(const StdBuf &Buf2, size_t iAt = 0) const
244 {
245 return Compare(pCData: Buf2.getData(), iCSize: Buf2.getSize(), iAt);
246 }
247
248 // Create a copy of the data (dereferences, obviously)
249 void Copy(size_t inSize)
250 {
251 if (isNull() && !inSize) return;
252 const void *pOldData = getData();
253 size_t iOldSize = iSize;
254 New(inSize);
255 Write(pnData: pOldData, inSize: (std::min)(a: iOldSize, b: inSize));
256 }
257
258 void Copy()
259 {
260 Copy(inSize: iSize);
261 }
262
263 // Copy data from address
264 void Copy(const void *pnData, size_t inSize)
265 {
266 Ref(pnData, inSize); Copy();
267 }
268
269 // Copy from another buffer
270 void Copy(const StdBuf &Buf2)
271 {
272 Copy(pnData: Buf2.getData(), inSize: Buf2.getSize());
273 }
274
275 // Create a copy and return it
276 StdBuf Duplicate() const
277 {
278 StdBuf Buf; Buf.Copy(Buf2: *this); return Buf;
279 }
280
281 // Append data from address
282 void Append(const void *pnData, size_t inSize)
283 {
284 Grow(iGrow: inSize);
285 Write(pnData, inSize, iAt: iSize - inSize);
286 }
287
288 // Append data from another buffer
289 void Append(const StdBuf &Buf2)
290 {
291 Append(pnData: Buf2.getData(), inSize: Buf2.getSize());
292 }
293
294 // Reference another buffer's contents
295 void Ref(const StdBuf &Buf2)
296 {
297 Ref(pnData: Buf2.getData(), inSize: Buf2.getSize());
298 }
299
300 // Create a reference to this buffer's contents
301 StdBuf getRef() const
302 {
303 return StdBuf(getData(), getSize(), false);
304 }
305
306 // take over another buffer's contents
307 void Take(StdBuf &Buf2)
308 {
309 Take(pnData: Buf2.GrabPointer(), inSize: Buf2.getSize());
310 }
311
312 void Take(StdBuf &&Buf2)
313 {
314 Take(pnData: Buf2.GrabPointer(), inSize: Buf2.getSize());
315 }
316
317 // * File support
318 bool LoadFromFile(const char *szFile);
319 bool SaveToFile(const char *szFile) const;
320
321 // *** Operators
322
323 // Null check
324 bool operator!() const { return isNull(); }
325
326 // Appending
327 StdBuf &operator+=(const StdBuf &Buf2)
328 {
329 Append(Buf2);
330 return *this;
331 }
332
333 StdBuf operator+(const StdBuf &Buf2) const
334 {
335 StdBuf Buf(getRef());
336 Buf.Append(Buf2);
337 return Buf;
338 }
339
340 // Compare
341 bool operator==(const StdBuf &Buf2) const
342 {
343 return getSize() == Buf2.getSize() && !Compare(Buf2);
344 }
345
346 // Set (take if possible)
347 StdBuf &operator=(StdBuf &&Buf2)
348 {
349 if (Buf2.isRef())
350 {
351 Ref(Buf2);
352 }
353 else
354 {
355 Take(Buf2: std::forward<StdBuf>(t&: Buf2));
356 }
357 return *this;
358 }
359
360 StdBuf &operator=(const StdBuf &Buf2) { Copy(Buf2); return *this; }
361
362 // *** Compiling
363
364 void CompileFunc(class StdCompiler *pComp, int iType = 0);
365
366 template<class elem_t>
367 const elem_t *getPtr(size_t pos = 0) const
368 {
369 return reinterpret_cast<const elem_t *>(reinterpret_cast<const char *>(getData()) + pos);
370 }
371
372 template<class elem_t>
373 elem_t *getMPtr(size_t pos = 0)
374 {
375 return reinterpret_cast<elem_t *>(reinterpret_cast<char *>(getMData()) + pos);
376 }
377};
378
379// Stringbuffer (operates on null-terminated character buffers)
380class StdStrBuf : protected StdBuf
381{
382public:
383 // *** Construction
384
385 StdStrBuf()
386 : StdBuf() {}
387
388 // references the string literal
389 template<size_t N>
390 StdStrBuf(const char(&str)[N])
391 : StdBuf(str, strlen(str) + 1, false) { }
392
393 // See StdBuf::StdBuf. Copies by default or references if desired.
394 StdStrBuf(const StdStrBuf &Buf2, bool fCopy = true) : StdBuf(Buf2, fCopy) {}
395 StdStrBuf(StdStrBuf &&Buf2, bool fCopy = false) : StdBuf(std::forward<StdStrBuf>(t&: Buf2), fCopy) {}
396
397 // Set by constant data. Copies by default or references if desired.
398 explicit StdStrBuf(const char *pData, bool fCopy = true)
399 : StdBuf(pData, pData ? strlen(s: pData) + 1 : 0, fCopy) {}
400
401 // As previous constructor, but set length manually.
402 StdStrBuf(const char *pData, size_t iLength, bool fCopy = true)
403 : StdBuf(pData, pData ? iLength + 1 : 0, fCopy) {}
404
405 static StdStrBuf MakeRef(const StdStrBuf &Buf2) { return Buf2.getRef(); }
406 static StdStrBuf MakeRef(const char *str) { return StdStrBuf(str, false); }
407
408public:
409 // *** Getters
410
411 bool isNull() const { return StdBuf::isNull(); }
412 const char *getData() const { return StdBuf::getPtr<char>(); }
413 char *getMData() { return StdBuf::getMPtr<char>(); }
414 size_t getSize() const { return StdBuf::getSize(); }
415 size_t getLength() const { return getSize() ? getSize() - 1 : 0; }
416 bool isRef() const { return StdBuf::isRef(); }
417
418 const char *getPtr(size_t i) const { return StdBuf::getPtr<char>(pos: i); }
419 char *getMPtr(size_t i) { return StdBuf::getMPtr<char>(pos: i); }
420
421 // For convenience. Note that writing can't be allowed.
422 char operator[](size_t i) const { return *getPtr(i); }
423
424 // Analogous to StdBuf
425 void Ref(const char *pnData) { StdBuf::Ref(pnData, inSize: pnData ? strlen(s: pnData) + 1 : 0); }
426 void Ref(const char *pnData, size_t iLength) { assert((!pnData && !iLength) || strlen(pnData) == iLength); StdBuf::Ref(pnData, inSize: iLength + 1); }
427 void Take(StdStrBuf &&Buf2) { StdBuf::Take(Buf2: std::forward<StdStrBuf>(t&: Buf2)); }
428 void Take(char *pnData) { StdBuf::Take(pnData, inSize: pnData ? strlen(s: pnData) + 1 : 0); }
429 void Take(char *pnData, size_t iLength) { assert((!pnData && !iLength) || strlen(pnData) == iLength); StdBuf::Take(pnData, inSize: iLength + 1); }
430 char *GrabPointer() { return reinterpret_cast<char *>(StdBuf::GrabPointer()); }
431
432 void Ref(const StdStrBuf &Buf2) { StdBuf::Ref(pnData: Buf2.getData(), inSize: Buf2.getSize()); }
433 StdStrBuf getRef() const { return StdStrBuf(getData(), getLength(), false); }
434 void Take(StdStrBuf &Buf2) { StdBuf::Take(Buf2); }
435
436 void Clear() { StdBuf::Clear(); }
437 void Copy() { StdBuf::Copy(); }
438 void Copy(const char *pnData) { StdBuf::Copy(pnData, inSize: pnData ? strlen(s: pnData) + 1 : 0); }
439 void Copy(const StdStrBuf &Buf2) { StdBuf::Copy(Buf2); }
440 StdStrBuf Duplicate() const { StdStrBuf Buf; Buf.Copy(Buf2: *this); return Buf; }
441 void Move(size_t iFrom, size_t inSize, size_t iTo = 0) { StdBuf::Move(iFrom, inSize, iTo); }
442
443 // Byte-wise compare (will compare characters up to the length of the second string)
444 int Compare(const StdStrBuf &Buf2, size_t iAt = 0) const
445 {
446 assert(iAt <= getLength());
447 return StdBuf::Compare(pCData: Buf2.getData(), iCSize: Buf2.getLength(), iAt);
448 }
449
450 // Grows the string to contain the specified number more/less characters.
451 // Note: Will set the terminator, but won't initialize - use Append* instead.
452 void Grow(size_t iGrow)
453 {
454 StdBuf::Grow(iGrow: getSize() ? iGrow : iGrow + 1);
455 *getMPtr(i: getLength()) = '\0';
456 }
457
458 void Shrink(size_t iShrink)
459 {
460 assert(iShrink <= getLength());
461 StdBuf::Shrink(iShrink);
462 *getMPtr(i: getLength()) = '\0';
463 }
464
465 void SetLength(size_t iLength)
466 {
467 if (iLength == getLength() && !isNull()) return;
468 if (iLength >= getLength())
469 Grow(iGrow: iLength - getLength());
470 else
471 Shrink(iShrink: getLength() - iLength);
472 }
473
474 // Append string
475 void Append(const char *pnData, size_t iChars)
476 {
477 Grow(iGrow: iChars);
478 Write(pnData, inSize: iChars, iAt: iSize - iChars - 1);
479 }
480
481 void Append(const char *pnData)
482 {
483 Append(pnData, iChars: strlen(s: pnData));
484 }
485
486 void Append(const StdStrBuf &Buf2)
487 {
488 Append(pnData: Buf2.getData(), iChars: Buf2.getLength());
489 }
490
491 // Copy string
492 void Copy(const char *pnData, size_t iChars)
493 {
494 Clear();
495 Append(pnData, iChars);
496 }
497
498 // * File support
499 bool LoadFromFile(const char *szFile);
500 bool SaveToFile(const char *szFile) const;
501
502 // * Operators
503
504 bool operator!() const { return isNull(); }
505
506 StdStrBuf &operator+=(const StdStrBuf &Buf2) { Append(Buf2); return *this; }
507 StdStrBuf &operator+=(const char *szString) { Append(pnData: szString); return *this; }
508 StdStrBuf operator+(const StdStrBuf &Buf2) const { StdStrBuf Buf = getRef(); Buf.Append(Buf2); return Buf; }
509 StdStrBuf operator+(const char *szString) const { StdStrBuf Buf = getRef(); Buf.Append(pnData: szString); return Buf; }
510
511 bool operator==(const StdStrBuf &Buf2) const
512 {
513 return getLength() == Buf2.getLength() && !Compare(Buf2);
514 }
515
516 bool operator==(const char *szString) const { return StdStrBuf(szString, false) == *this; }
517
518 // Note this references the data.
519 StdStrBuf &operator=(const StdStrBuf &Buf2) { Copy(Buf2); return *this; }
520
521 template<std::convertible_to<const char *> T>
522 StdStrBuf &operator=(T szString) { Copy(szString); return *this; }
523
524 template<size_t N>
525 StdStrBuf &operator=(const char (&szString)[N]) { Ref(szString); return *this; }
526
527 explicit operator bool() const { return getData(); }
528
529 // less-than operation for map
530 inline bool operator<(const StdStrBuf &v2)
531 {
532 size_t iLen = getLength(), iLen2 = v2.getLength();
533 if (iLen == iLen2)
534 return iLen ? (strcmp(s1: getData(), s2: v2.getData()) < 0) : false;
535 else
536 return iLen < iLen2;
537 }
538
539 // * String specific
540
541 void AppendChars(char cChar, size_t iCnt)
542 {
543 Grow(iGrow: iCnt);
544 for (size_t i = getLength() - iCnt; i < getLength(); i++)
545 *getMPtr(i) = cChar;
546 }
547
548 void AppendChar(char cChar)
549 {
550 AppendChars(cChar, iCnt: 1);
551 }
552
553 void InsertChar(char cChar, size_t insert_before)
554 {
555 assert(insert_before <= getLength());
556 Grow(iGrow: 1);
557 for (size_t i = getLength() - 1; i > insert_before; --i)
558 *getMPtr(i) = *getPtr(i: i - 1);
559 *getMPtr(i: insert_before) = cChar;
560 }
561
562 // Append data until given character (or string end) occurs.
563 void AppendUntil(const char *szString, char cUntil)
564 {
565 const char *pPos = strchr(s: szString, c: cUntil);
566 if (pPos)
567 Append(pnData: szString, iChars: pPos - szString);
568 else
569 Append(pnData: szString);
570 }
571
572 // See above
573 void CopyUntil(const char *szString, char cUntil)
574 {
575 Clear();
576 AppendUntil(szString, cUntil);
577 }
578
579 // cut end after given char into another string. Return whether char was found at all
580 bool SplitAtChar(char cSplit, StdStrBuf *psSplit)
581 {
582 if (!getData()) return false;
583 const char *pPos = strchr(s: getData(), c: cSplit);
584 if (!pPos) return false;
585 size_t iPos = pPos - getData();
586 if (psSplit) psSplit->Take(Buf2: copyPart(iStart: iPos + 1, inSize: getLength() - iPos - 1));
587 Shrink(iShrink: getLength() - iPos);
588 return true;
589 }
590
591 StdStrBuf copyPart(size_t iStart, size_t inSize) const
592 {
593 assert(iStart + inSize <= iSize);
594 if (!inSize) return StdStrBuf();
595 StdStrBuf sResult;
596 sResult.Copy(pnData: getPtr(i: iStart), iChars: inSize);
597 return sResult;
598 }
599
600 // replace all occurences of one string with another. Return number of replacements.
601 int Replace(const char *szOld, const char *szNew, size_t iStartSearch = 0);
602 int ReplaceChar(char cOld, char cNew, size_t iStartSearch = 0);
603
604 // replace the trailing part of a string with something else
605 void ReplaceEnd(size_t iPos, const char *szNewEnd);
606
607 // get an indexed section from the string like Section1;Section2;Section3
608 bool GetSection(size_t idx, StdStrBuf *psOutSection, char cSeparator = ';') const;
609
610 // Checks wether the contents are valid UTF-8, and if not, convert them from windows-1252 to UTF-8.
611 void EnsureUnicode();
612
613 // check if a string consists only of the given chars
614 bool ValidateChars(const char *szInitialChars, const char *szMidChars);
615
616 void EscapeString()
617 {
618 Replace(szOld: "\\", szNew: "\\\\");
619 Replace(szOld: "\"", szNew: "\\\"");
620 }
621
622 bool TrimSpaces(); // kill spaces at beginning and end. Return if changed.
623
624 // * Compiling
625 void CompileFunc(class StdCompiler *pComp, int iRawType = 0);
626};
627