1/*
2 * LegacyClonk
3 *
4 * Copyright (c) 1998-2000, Matthes Bender (RedWolf Design)
5 * Copyright (c) 2017-2020, 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#include "C4Strings.h"
18
19#include "C4Math.h"
20
21#include <algorithm>
22#include <cstring>
23#include <cctype>
24
25// Characters
26
27char CharCapital(char cChar)
28{
29 if (Inside(ival: cChar, lbound: 'a', rbound: 'z')) return (cChar - 32);
30 if (cChar == '\xe4' /*ä*/) return '\xc4' /*Ä*/;
31 if (cChar == '\xf6' /*ö*/) return '\xd6' /*Ö*/;
32 if (cChar == '\xfc' /*ü*/) return '\xdc' /*Ü*/;
33 return cChar;
34}
35
36bool IsIdentifier(char cChar)
37{
38 if (Inside(ival: cChar, lbound: 'A', rbound: 'Z')) return true;
39 if (Inside(ival: cChar, lbound: 'a', rbound: 'z')) return true;
40 if (Inside(ival: cChar, lbound: '0', rbound: '9')) return true;
41 if (cChar == '_') return true;
42 if (cChar == '~') return true;
43 if (cChar == '+') return true;
44 if (cChar == '-') return true;
45 return false;
46}
47
48bool IsWhiteSpace(char cChar)
49{
50 if (cChar == ' ') return true;
51 if (cChar == 0x09) return true; // Tab
52 if (cChar == 0x0D) return true; // Line feed
53 if (cChar == 0x0A) return true; // Line feed
54 return false;
55}
56
57// Strings
58
59size_t SLen(const char *sptr)
60{
61 if (!sptr) return 0;
62 return strlen(s: sptr);
63}
64
65void SCopyL(const char *szSource, char *sTarget, size_t iMaxL)
66{
67 if (szSource == sTarget) return;
68 if (!sTarget) return;
69 *sTarget = 0;
70 if (!szSource) return;
71 for (; *szSource && iMaxL > 0; --iMaxL)
72 {
73 *sTarget++ = *szSource++;
74 }
75 *sTarget = 0;
76}
77
78void SCopy(const char *szSource, char *sTarget, size_t iMaxL)
79{
80 SCopyL(szSource, sTarget, iMaxL);
81}
82
83void SCopyUntil(const char *szSource, char *sTarget, char cUntil, size_t iMaxL, size_t iIndex)
84{
85 if (szSource == sTarget) return;
86 if (!sTarget) return;
87 *sTarget = 0;
88 if (!szSource) return;
89 for (; *szSource && ((*szSource != cUntil) || (iIndex > 0)) && iMaxL > 0; --iMaxL)
90 {
91 if (*szSource == cUntil) --iIndex;
92 *sTarget++ = *szSource++;
93 }
94 *sTarget = 0;
95}
96
97void SCopyUntil(const char *szSource, char *sTarget, const char *sUntil, size_t iMaxL)
98{
99 size_t n = std::min(a: strcspn(s: szSource, reject: sUntil), b: iMaxL - 1);
100 strncpy(dest: sTarget, src: szSource, n: n);
101 sTarget[n] = 0;
102}
103
104bool SEqual(const char *szStr1, const char *szStr2)
105{
106 if (!szStr1 || !szStr2) return false;
107 return !strcmp(s1: szStr1, s2: szStr2);
108}
109
110// Beginning of string 1 needs to match string 2.
111
112bool SEqual2(const char *szStr1, const char *szStr2)
113{
114 if (!szStr1 || !szStr2) return false;
115 while (*szStr1 && *szStr2)
116 if (*szStr1++ != *szStr2++) return false;
117 if (*szStr2) return false; // Str1 is shorter
118 return true;
119}
120
121bool SEqualNoCase(const char *szStr1, const char *szStr2, size_t iLen)
122{
123 if (!szStr1 || !szStr2) return false;
124 if (iLen == 0) return true;
125 while (*szStr1 && *szStr2)
126 {
127 if (CharCapital(cChar: *szStr1++) != CharCapital(cChar: *szStr2++)) return false;
128 if (iLen > 0) { iLen--; if (iLen == 0) return true; }
129 }
130 if (*szStr1 || *szStr2) return false;
131 return true;
132}
133
134bool SEqual2NoCase(const char *szStr1, const char *szStr2, size_t iLen)
135{
136 if (!szStr1 || !szStr2) return false;
137 if (iLen == 0) return true;
138 while (*szStr1 && *szStr2)
139 {
140 if (CharCapital(cChar: *szStr1++) != CharCapital(cChar: *szStr2++)) return false;
141 if (iLen > 0) { iLen--; if (iLen == 0) return true; }
142 }
143 if (*szStr2) return false; // Str1 is shorter
144 return true;
145}
146
147int SCharPos(char cTarget, const char *szInStr, size_t iIndex)
148{
149 const char *cpos;
150 int ccpos;
151 if (!szInStr) return -1;
152 for (cpos = szInStr, ccpos = 0; *cpos; cpos++, ccpos++)
153 if (*cpos == cTarget)
154 if (iIndex == 0) return ccpos;
155 else --iIndex;
156 return -1;
157}
158
159int SCharLastPos(char cTarget, const char *szInStr)
160{
161 const char *cpos;
162 int ccpos, lcpos;
163 if (!szInStr) return -1;
164 for (cpos = szInStr, ccpos = 0, lcpos = -1; *cpos; cpos++, ccpos++)
165 if (*cpos == cTarget) lcpos = ccpos;
166 return lcpos;
167}
168
169void SAppend(const char *szSource, char *szTarget, size_t iMaxL)
170{
171 if (iMaxL == -1)
172 SCopy(szSource, sTarget: szTarget + SLen(sptr: szTarget));
173 else
174 SCopy(szSource, sTarget: szTarget + SLen(sptr: szTarget), iMaxL: iMaxL - SLen(sptr: szTarget));
175}
176
177void SAppendChar(char cChar, char *szStr)
178{
179 if (!szStr) return;
180 char *cPos;
181 for (cPos = szStr; *cPos; cPos++);
182 *cPos = cChar; *(cPos + 1) = 0;
183}
184
185bool SCopySegment(const char *szString, size_t iSegment, char *sTarget,
186 char cSeparator, size_t iMaxL, bool fSkipWhitespace)
187{
188 // Advance to indexed segment
189 while (iSegment > 0)
190 {
191 if (SCharPos(cTarget: cSeparator, szInStr: szString) == -1)
192 {
193 sTarget[0] = 0; return false;
194 }
195 szString += SCharPos(cTarget: cSeparator, szInStr: szString) + 1;
196 iSegment--;
197 }
198 // Advance whitespace
199 if (fSkipWhitespace)
200 szString = SAdvanceSpace(szSPos: szString);
201 // Copy segment contents
202 SCopyUntil(szSource: szString, sTarget, cUntil: cSeparator, iMaxL);
203 return true;
204}
205
206bool SCopySegmentEx(const char *szString, size_t iSegment, char *sTarget,
207 char cSep1, char cSep2, size_t iMaxL, bool fSkipWhitespace)
208{
209 // Advance to indexed segment
210 while (iSegment > 0)
211 {
212 // use the separator that's closer
213 int iPos1 = SCharPos(cTarget: cSep1, szInStr: szString), iPos2 = SCharPos(cTarget: cSep2, szInStr: szString);
214 if (iPos1 == -1)
215 if (iPos2 == -1)
216 {
217 sTarget[0] = 0; return false;
218 }
219 else
220 iPos1 = iPos2;
221 else if (iPos2 != -1 && iPos2 < iPos1)
222 iPos1 = iPos2;
223 szString += iPos1 + 1;
224 iSegment--;
225 }
226 // Advance whitespace
227 if (fSkipWhitespace)
228 szString = SAdvanceSpace(szSPos: szString);
229 // Copy segment contents; use separator that's closer
230 int iPos1 = SCharPos(cTarget: cSep1, szInStr: szString), iPos2 = SCharPos(cTarget: cSep2, szInStr: szString);
231 if (iPos2 != -1 && (iPos2 < iPos1 || iPos1 == -1)) cSep1 = cSep2;
232 SCopyUntil(szSource: szString, sTarget, cUntil: cSep1, iMaxL);
233 return true;
234}
235
236int SCharCount(char cTarget, const char *szInStr, const char *cpUntil)
237{
238 int iResult = 0;
239 // Scan string
240 while (*szInStr)
241 {
242 // End position reached (end character is not included)
243 if (szInStr == cpUntil) return iResult;
244 // Character found
245 if (*szInStr == cTarget) iResult++;
246 // Advance
247 szInStr++;
248 }
249 // Done
250 return iResult;
251}
252
253int SCharCountEx(const char *szString, const char *szCharList)
254{
255 int iResult = 0;
256 while (*szCharList)
257 {
258 iResult += SCharCount(cTarget: *szCharList, szInStr: szString);
259 szCharList++;
260 }
261 return iResult;
262}
263
264void SReplaceChar(char *str, char fc, char tc)
265{
266 while (str && *str)
267 {
268 if (*str == fc) *str = tc; str++;
269 }
270}
271
272void SCapitalize(char *str)
273{
274 while (str && *str)
275 {
276 *str = CharCapital(cChar: *str);
277 str++;
278 }
279}
280
281const char *SSearch(const char *szString, const char *szIndex)
282{
283 const char *cscr;
284 size_t match = 0;
285 if (!szString || !szIndex) return nullptr;
286 const auto indexlen = SLen(sptr: szIndex);
287 for (cscr = szString; cscr && *cscr; cscr++)
288 {
289 if (*cscr == szIndex[match]) match++;
290 else match = 0;
291 if (match >= indexlen) return cscr + 1;
292 }
293 return nullptr;
294}
295
296const char *SSearchNoCase(const char *szString, const char *szIndex)
297{
298 const char *cscr;
299 size_t match = 0;
300 if (!szString || !szIndex) return nullptr;
301 const auto indexlen = SLen(sptr: szIndex);
302 for (cscr = szString; cscr && *cscr; cscr++)
303 {
304 if (CharCapital(cChar: *cscr) == CharCapital(cChar: szIndex[match])) match++;
305 else match = 0;
306 if (match >= indexlen) return cscr + 1;
307 }
308 return nullptr;
309}
310
311void SWordWrap(char *szText, char cSpace, char cSepa, size_t iMaxLine)
312{
313 if (!szText) return;
314 // Scan string
315 char *cPos, *cpLastSpace = nullptr;
316 size_t iLineRun = 0;
317 for (cPos = szText; *cPos; cPos++)
318 {
319 // Store last space
320 if (*cPos == cSpace) cpLastSpace = cPos;
321 // Separator encountered: reset line run
322 if (*cPos == cSepa) iLineRun = 0;
323 // Need a break, insert at last space
324 if (iLineRun >= iMaxLine)
325 if (cpLastSpace)
326 {
327 *cpLastSpace = cSepa; iLineRun = cPos - cpLastSpace;
328 }
329 // Line run
330 iLineRun++;
331 }
332}
333
334const char *SAdvanceSpace(const char *szSPos)
335{
336 if (!szSPos) return nullptr;
337 while (IsWhiteSpace(cChar: *szSPos)) szSPos++;
338 return szSPos;
339}
340
341const char *SAdvancePast(const char *szSPos, char cPast)
342{
343 if (!szSPos) return nullptr;
344 while (*szSPos)
345 {
346 if (*szSPos == cPast) { szSPos++; break; }
347 szSPos++;
348 }
349 return szSPos;
350}
351
352void SCopyIdentifier(const char *szSource, char *sTarget, size_t iMaxL)
353{
354 if (!szSource || !sTarget) return;
355 for (; iMaxL > 0 && IsIdentifier(cChar: *szSource); --iMaxL)
356 {
357 *sTarget++ = *szSource++;
358 }
359 *sTarget = 0;
360}
361
362int SClearFrontBack(char *szString, char cClear)
363{
364 int cleared = 0;
365 char *cpos;
366 if (!szString) return 0;
367 for (cpos = szString; *cpos && (*cpos == cClear); cpos++, cleared++);
368 // strcpy is undefined when used on overlapping strings...
369 if (cpos != szString) memmove(dest: szString, src: cpos, n: SLen(sptr: cpos) + 1);
370 for (cpos = szString + SLen(sptr: szString) - 1; (cpos > szString) && (*cpos == cClear); cpos--, cleared++)
371 *cpos = 0x00;
372 return cleared;
373}
374
375void SNewSegment(char *szStr, const char *szSepa)
376{
377 if (szStr[0]) SAppend(szSource: szSepa, szTarget: szStr);
378}
379
380int SGetLine(const char *szText, const char *cpPosition)
381{
382 if (!szText || !cpPosition) return 0;
383 int iLines = 0;
384 while (*szText && (szText < cpPosition))
385 {
386 if (*szText == 0x0A) iLines++;
387 szText++;
388 }
389 return iLines;
390}
391
392int SLineGetCharacters(const char *szText, const char *cpPosition)
393{
394 if (!szText || !cpPosition) return 0;
395 int iChars = 0;
396 while (*szText && (szText < cpPosition))
397 {
398 if (*szText == 0x0A) iChars = 0;
399 iChars++;
400 szText++;
401 }
402 return iChars;
403}
404
405void SInsert(char *szString, const char *szInsert, size_t iPosition, size_t iMaxLen)
406{
407 // Safety
408 if (!szString || !szInsert || !szInsert[0]) return;
409 size_t insertlen = strlen(s: szInsert);
410 if (strlen(s: szString) + insertlen > iMaxLen) return;
411 // Move up string remainder
412 memmove(dest: szString + iPosition + insertlen, src: szString + iPosition, n: SLen(sptr: szString + iPosition) + 1);
413 // Copy insertion
414 std::memmove(dest: szString + iPosition, src: szInsert, n: SLen(sptr: szInsert));
415}
416
417void SDelete(char *szString, size_t iLen, size_t iPosition)
418{
419 // Safety
420 if (!szString) return;
421 // Move down string remainder
422 std::memmove(dest: szString + iPosition, src: szString + iPosition + iLen, n: SLen(sptr: szString + iPosition + iLen) + 1);
423}
424
425bool SCopyEnclosed(const char *szSource, char cOpen, char cClose, char *sTarget, size_t iSize)
426{
427 int iPos, iLen;
428 if (!szSource || !sTarget) return false;
429 if ((iPos = SCharPos(cTarget: cOpen, szInStr: szSource)) < 0) return false;
430 if ((iLen = SCharPos(cTarget: cClose, szInStr: szSource + iPos + 1)) < 0) return false;
431 SCopy(szSource: szSource + iPos + 1, sTarget, iMaxL: std::min<size_t>(a: iLen, b: iSize));
432 return true;
433}
434
435bool SGetModule(const char *szList, size_t iIndex, char *sTarget, size_t iSize)
436{
437 if (!szList || !sTarget) return false;
438 if (!SCopySegment(szString: szList, iSegment: iIndex, sTarget, cSeparator: ';', iMaxL: iSize)) return false;
439 SClearFrontBack(szString: sTarget);
440 return true;
441}
442
443bool SIsModule(const char *szList, const char *szString, size_t *ipIndex, bool fCaseSensitive)
444{
445 char szModule[1024 + 1];
446 // Compare all modules
447 for (size_t iMod = 0; SGetModule(szList, iIndex: iMod, sTarget: szModule, iSize: 1024); iMod++)
448 if (fCaseSensitive ? SEqual(szStr1: szString, szStr2: szModule) : SEqualNoCase(szStr1: szString, szStr2: szModule))
449 {
450 // Provide index if desired
451 if (ipIndex) *ipIndex = iMod;
452 // Found
453 return true;
454 }
455 // Not found
456 return false;
457}
458
459bool SAddModule(char *szList, const char *szModule, bool fCaseSensitive)
460{
461 // Safety / no empties
462 if (!szList || !szModule || !szModule[0]) return false;
463 // Already a module?
464 if (SIsModule(szList, szString: szModule, ipIndex: nullptr, fCaseSensitive)) return false;
465 // New segment, add string
466 SNewSegment(szStr: szList);
467 SAppend(szSource: szModule, szTarget: szList);
468 // Success
469 return true;
470}
471
472bool SAddModules(char *szList, const char *szModules, bool fCaseSensitive)
473{
474 // Safety / no empties
475 if (!szList || !szModules || !szModules[0]) return false;
476 // Add modules
477 char szModule[1024 + 1]; // limited
478 for (size_t cnt = 0; SGetModule(szList: szModules, iIndex: cnt, sTarget: szModule, iSize: 1024); cnt++)
479 SAddModule(szList, szModule, fCaseSensitive);
480 // Success
481 return true;
482}
483
484bool SRemoveModule(char *szList, const char *szModule, bool fCaseSensitive)
485{
486 size_t iMod, iPos, iLen;
487 // Not a module
488 if (!SIsModule(szList, szString: szModule, ipIndex: &iMod, fCaseSensitive)) return false;
489 // Get module start
490 iPos = 0;
491 if (iMod > 0) iPos = SCharPos(cTarget: ';', szInStr: szList, iIndex: iMod - 1) + 1;
492 // Get module length
493 iLen = SCharPos(cTarget: ';', szInStr: szList + iPos);
494 if (iLen < 0) iLen = SLen(sptr: szList); else iLen += 1;
495 // Delete module
496 SDelete(szString: szList, iLen, iPosition: iPos);
497 // Success
498 return true;
499}
500
501bool SRemoveModules(char *szList, const char *szModules, bool fCaseSensitive)
502{
503 // Safety / no empties
504 if (!szList || !szModules || !szModules[0]) return false;
505 // Remove modules
506 char szModule[1024 + 1]; // limited
507 for (int cnt = 0; SGetModule(szList: szModules, iIndex: cnt, sTarget: szModule, iSize: 1024); cnt++)
508 SRemoveModule(szList, szModule, fCaseSensitive);
509 // Success
510 return true;
511}
512
513int SModuleCount(const char *szList)
514{
515 if (!szList) return 0;
516 int iCount = 0;
517 bool fNewModule = true;
518 while (*szList)
519 {
520 switch (*szList)
521 {
522 case ' ': break;
523 case ';': fNewModule = true; break;
524 default: if (fNewModule) iCount++; fNewModule = false; break;
525 }
526 szList++;
527 }
528 return iCount;
529}
530
531bool SWildcardMatchEx(const char *szString, const char *szWildcard)
532{
533 // safety
534 if (!szString || !szWildcard) return false;
535 // match char-wise
536 const char *pWild = szWildcard, *pPos = szString;
537 const char *pLWild = nullptr, *pLPos = nullptr; // backtracking
538 while (*pWild || pLWild)
539 // string wildcard?
540 if (*pWild == '*')
541 {
542 pLWild = ++pWild; pLPos = pPos;
543 }
544 // nothing left to match?
545 else if (!*pPos)
546 break;
547 // equal or one-character-wildcard? proceed
548 else if (*pWild == '?' || *pWild == *pPos)
549 {
550 pWild++; pPos++;
551 }
552 // backtrack possible?
553 else if (pLPos)
554 {
555 pWild = pLWild; pPos = ++pLPos;
556 }
557 // match failed
558 else
559 return false;
560 // match complete if both strings are fully matched
561 return !*pWild && !*pPos;
562}
563
564const char *SGetParameter(const char *strCommandLine, size_t iParameter, char *strTarget, size_t iSize, bool *pWasQuoted)
565{
566 // Parse command line which may contain spaced or quoted parameters
567 static char strParameter[2048 + 1];
568 const char *c = strCommandLine;
569 bool fQuoted;
570 while (c && *c)
571 {
572 // Quoted parameter
573 if (fQuoted = (*c == '"'))
574 {
575 SCopyUntil(szSource: ++c, sTarget: strParameter, cUntil: '"', iMaxL: 2048);
576 c += SLen(sptr: strParameter);
577 if (*c == '"') c++;
578 }
579 // Spaced parameter
580 else
581 {
582 bool fWrongQuote = (SCharPos(cTarget: '"', szInStr: c) > -1) && (SCharPos(cTarget: '"', szInStr: c) < SCharPos(cTarget: ' ', szInStr: c));
583 SCopyUntil(szSource: c, sTarget: strParameter, cUntil: fWrongQuote ? '"' : ' ', iMaxL: 2048);
584 c += std::max<size_t>(a: SLen(sptr: strParameter), b: 1);
585 }
586 // Process (non-empty) parameter
587 if (strParameter[0])
588 {
589 // Success
590 if (iParameter == 0)
591 {
592 if (strTarget) SCopy(szSource: strParameter, sTarget: strTarget, iMaxL: iSize);
593 if (pWasQuoted) *pWasQuoted = fQuoted;
594 return strParameter;
595 }
596 // Continue
597 else
598 iParameter--;
599 }
600 }
601 // Not found
602 return nullptr;
603}
604