source: cpp/common/util-string.cpp

Last change on this file was 1288, checked in by Maciej Komosinski, 5 months ago

Added helper functions

  • Property svn:eol-style set to native
File size: 6.3 KB
Line 
1// This file is a part of Framsticks SDK.  http://www.framsticks.com/
2// Copyright (C) 1999-2023  Maciej Komosinski and Szymon Ulatowski.
3// See LICENSE.txt for details.
4
5#include "util-string.h"
6#include <stdarg.h>
7#include "nonstd_stdio.h"
8#include "nonstd.h"
9#include <assert.h>
10#include <cstdlib> //malloc()
11#include <algorithm>
12#ifdef USE_VIRTFILE
13#include <common/virtfile/virtfile.h>
14#endif
15#ifdef __ANDROID__
16#include <android/log.h> //only needed to print error messages related to a workaround for Android bug
17#endif
18
19string repr(const char *str)
20{
21        string out = "";
22        char hex[4];
23        while (*str)
24        {
25                unsigned char ch = *str;
26                if (ch >= 32 && ch < 128)
27                        out += ch;
28                else
29                {
30                        if (ch == 10) out += "\\n"; else
31                                if (ch == 13) out += "\\r"; else
32                                {
33                                        sprintf(hex, "%X", ch);
34                                        out += "\\x";
35                                        out += hex;
36                                }
37                }
38                str++;
39        }
40        return out;
41}
42
43
44string ssprintf_va(const char* format, va_list ap)
45{
46        string s; //clang crashed when this declaration was in s=buf
47        int size = 256;
48        char* buf;
49        va_list ap_copy; // "va_list ap" can only by used once by printf-type functions as they advance the current argument pointer (crashed on linux x86_64)
50        // (does not apply to SString::sprintf, it does not have the va_list variant)
51
52        //almost like SString::sprintf, but there is no common code to share because SString can use its directWrite to avoid double allocating/copying
53#ifdef USE_VSCPRINTF
54        va_copy(ap_copy, ap);
55        size = _vscprintf(format, ap_copy) + 1; //+1 for terminating null character
56        va_end(ap_copy);
57#endif
58
59        while (true)
60        {
61                buf = (char*)malloc(size);
62                assert(buf != NULL);
63                va_copy(ap_copy, ap);
64                int n = vsnprintf(buf, size, format, ap_copy);
65                va_end(ap_copy);
66
67#ifdef __ANDROID__
68                //Workaround for Android bug. /system/lib64/libc.so? maybe only arm 64-bit? "If an encoding error occurs, a negative number is returned". On some devices keeps returning -1 forever.
69                //https://github.com/android-ndk/ndk/issues/879 but unfortunately during google play tests (Firebase Test Lab) this problem turned out to be not limited to Chinese devices and occurred in Mate 9, Galaxy S9, Pixel, Pixel 2, Moto Z (even with the en_GB locale; the locale is not important but the problem seem to be utf8 non-ascii chars in the format string).
70                if (n < 0 && size >= (1 << 24)) //wants more than 16M
71                {
72                        buf[size - 1] = 0; //just to ensure there is at least some ending \0 in memory... who knows what buggy vsnprintf() did.
73                        __android_log_print(ANDROID_LOG_ERROR, LOG_APP_NAME, "Giving up due to Android bug: vsnprintf() wants more than %d bytes, it used %zu bytes, for format='%s'", size, strlen(buf), format);
74                        //in my tests, it always used 0 bytes, so it produced a 0-length string: ""
75                        va_copy(ap_copy, ap);
76                        n = vsprintf(buf, format, ap_copy); //hoping 16M is enough
77                        va_end(ap_copy);
78                        __android_log_print(ANDROID_LOG_INFO, LOG_APP_NAME, "Fallback to vsprintf() produced string: '%s'", buf);
79                        if (n < 0) //vsprintf was also buggy. If we were strict, we should abort the app now.
80                        {
81                                strcpy(buf, "[STR_ERR] "); //a special prefix just to indicate the returned string is incorrect
82                                strcat(buf, format); //append and return the original formatting string
83                                __android_log_print(ANDROID_LOG_ERROR, LOG_APP_NAME, "vsprintf() also failed, using the incorrect resulting string: '%s'", buf);
84                        }
85                        n = strlen(buf); //pretend vsnprintf() or vsprintf() was OK to exit the endless loop
86                }
87#endif
88
89                if (n > -1 && n < size)
90                {
91                        s = buf;
92                        free(buf);
93                        return s;
94                }
95#ifdef VSNPRINTF_RETURNS_REQUIRED_SIZE
96                if (n > -1)    /* glibc 2.1 */
97                        size = n + 1; /* precisely what is needed */
98                else           /* glibc 2.0 */
99#endif
100                        size *= 2;  /* twice the old size */
101                free(buf);
102        }
103}
104
105bool str_starts_with(const char *str, const char *prefix)
106{
107        return strncmp(str, prefix, strlen(prefix)) == 0;
108}
109
110bool ends_with(std::string_view str, std::string_view suffix)
111{
112        return str.size() >= suffix.size() && 0 == str.compare(str.size() - suffix.size(), suffix.size(), suffix);
113}
114
115bool strip_prefix(string& modify_me, const char* prefix)
116{
117        if (starts_with(modify_me, prefix))
118        {
119                modify_me = modify_me.substr(strlen(prefix));
120                return true;
121        }
122        return false;
123}
124
125char* strmove(char *a, char *b) //strcpy that works well for overlapping strings ("Source and destination overlap")
126{
127        if (a == NULL || b == NULL)
128                return NULL;
129        memmove(a, b, strlen(b) + 1);
130        return a;
131}
132
133string ssprintf(const char* format, ...)
134{
135        va_list ap;
136        va_start(ap, format);
137        string ret = ssprintf_va(format, ap); //is it too wasteful? copying the string again... unless the compiler can handle it better
138        va_end(ap);
139        return ret;
140}
141
142string stripExt(const string& filename)
143{
144        size_t dot = filename.rfind('.');
145        if (dot == string::npos) return filename;
146        size_t sep = filename.rfind(PATH_SEPARATOR_CHAR);
147        if ((sep == string::npos) || (sep < dot))
148                return filename.substr(0, dot);
149        return filename;
150}
151
152string stripFileDir(const string& filename)
153{
154        size_t sep = filename.rfind(PATH_SEPARATOR_CHAR);
155        if (sep == string::npos) return filename;
156        return filename.substr(sep + 1);
157}
158
159string getFileExt(const string& filename)
160{
161        size_t dot = filename.rfind('.');
162        if (dot == string::npos) return string("");
163        size_t sep = filename.rfind(PATH_SEPARATOR_CHAR);
164        if ((sep == string::npos) || (sep < dot))
165                return filename.substr(dot);
166        return string("");
167}
168
169string getFileDir(const string& filename)
170{
171        size_t slash = filename.rfind(PATH_SEPARATOR_CHAR);
172        if (slash == string::npos) return string("");
173        return (slash == 0) ? string(PATH_SEPARATOR_STRING) : filename.substr(0, slash);
174}
175
176string concatPath(const string& path, const string& other_path)
177{
178        string result = path;
179        if (!result.empty() && result.back() != PATH_SEPARATOR_CHAR && !other_path.empty())
180                result += PATH_SEPARATOR_CHAR;
181        result += other_path;
182        return result;
183}
184
185//trimming functions, https://stackoverflow.com/questions/216823/whats-the-best-way-to-trim-stdstring
186
187void ltrim_inplace(string &s)
188{
189        s.erase(s.begin(), find_if(s.begin(), s.end(), [](int ch) {
190                return !isspace(ch);
191                }));
192}
193
194void rtrim_inplace(string &s)
195{
196        s.erase(find_if(s.rbegin(), s.rend(), [](int ch) {
197                return !isspace(ch);
198                }).base(), s.end());
199}
200
201void trim_inplace(string &s)
202{
203        ltrim_inplace(s);
204        rtrim_inplace(s);
205}
206
207string ltrim(string s)
208{
209        ltrim_inplace(s);
210        return s;
211}
212
213string rtrim(string s)
214{
215        rtrim_inplace(s);
216        return s;
217}
218
219string trim(string s)
220{
221        trim_inplace(s);
222        return s;
223}
Note: See TracBrowser for help on using the repository browser.