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 | |
---|
19 | string 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 | |
---|
44 | string 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 | |
---|
105 | bool str_starts_with(const char *str, const char *prefix) |
---|
106 | { |
---|
107 | return strncmp(str, prefix, strlen(prefix)) == 0; |
---|
108 | } |
---|
109 | |
---|
110 | bool 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 | |
---|
115 | bool 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 | |
---|
125 | char* 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 | |
---|
133 | string 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 | |
---|
142 | string 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 | |
---|
152 | string 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 | |
---|
159 | string 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 | |
---|
169 | string 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 | |
---|
176 | string 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 | |
---|
187 | void 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 | |
---|
194 | void 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 | |
---|
201 | void trim_inplace(string &s) |
---|
202 | { |
---|
203 | ltrim_inplace(s); |
---|
204 | rtrim_inplace(s); |
---|
205 | } |
---|
206 | |
---|
207 | string ltrim(string s) |
---|
208 | { |
---|
209 | ltrim_inplace(s); |
---|
210 | return s; |
---|
211 | } |
---|
212 | |
---|
213 | string rtrim(string s) |
---|
214 | { |
---|
215 | rtrim_inplace(s); |
---|
216 | return s; |
---|
217 | } |
---|
218 | |
---|
219 | string trim(string s) |
---|
220 | { |
---|
221 | trim_inplace(s); |
---|
222 | return s; |
---|
223 | } |
---|