1 | // This file is part of SmallBASIC |
2 | // |
3 | // Copyright(C) 2001-2019 Chris Warren-Smith. |
4 | // |
5 | // This program is distributed under the terms of the GPL v2.0 or later |
6 | // Download the GNU Public License (GPL) from www.gnu.org |
7 | // |
8 | |
9 | #include "ui/strlib.h" |
10 | #include <sys/types.h> |
11 | #include <sys/stat.h> |
12 | #include <unistd.h> |
13 | |
14 | using namespace strlib; |
15 | |
16 | //--String---------------------------------------------------------------------- |
17 | |
18 | String::String() : _buffer(nullptr) { |
19 | } |
20 | |
21 | String::String(const char *s) { |
22 | _buffer = (s == nullptr ? nullptr : strdup(s)); |
23 | } |
24 | |
25 | String::String(const String &s) { |
26 | _buffer = s._buffer == nullptr ? nullptr : strdup(s._buffer); |
27 | } |
28 | |
29 | String::String(const char *s, int len) : _buffer(nullptr) { |
30 | append(s, len); |
31 | } |
32 | |
33 | String::~String() { |
34 | free(_buffer); |
35 | _buffer = nullptr; |
36 | } |
37 | |
38 | const String &String::operator=(const String &s) { |
39 | clear(); |
40 | if (_buffer != s._buffer && s._buffer != nullptr) { |
41 | _buffer = strdup(s._buffer); |
42 | } |
43 | return *this; |
44 | } |
45 | |
46 | const String &String::operator=(const char *s) { |
47 | clear(); |
48 | if (_buffer != s) { |
49 | _buffer = strdup(s); |
50 | } |
51 | return *this; |
52 | } |
53 | |
54 | const void String::operator+=(const String &s) { |
55 | append(s._buffer); |
56 | } |
57 | |
58 | const void String::operator+=(const char *s) { |
59 | append(s); |
60 | } |
61 | |
62 | String &String::append(const String &s) { |
63 | append(s._buffer); |
64 | return *this; |
65 | } |
66 | |
67 | String &String::append(const String *s) { |
68 | if (s && !s->empty()) { |
69 | append(s->_buffer); |
70 | } |
71 | return *this; |
72 | } |
73 | |
74 | String &String::append(int i) { |
75 | char t[20]; |
76 | sprintf(t, "%i" , i); |
77 | append(t); |
78 | return *this; |
79 | } |
80 | |
81 | String &String::append(char c) { |
82 | char t[2] = { c, 0 }; |
83 | append(t); |
84 | return *this; |
85 | } |
86 | |
87 | String &String::append(const char *s) { |
88 | if (s != nullptr && s[0]) { |
89 | int len = length(); |
90 | _buffer = (char *)realloc(_buffer, len + strlen(s) + 1); |
91 | strcpy(_buffer + len, s); |
92 | } |
93 | return *this; |
94 | } |
95 | |
96 | String &String::append(const char *s, int numCopy) { |
97 | if (s != nullptr && numCopy) { |
98 | int len = strlen(s); |
99 | if (numCopy > len) { |
100 | numCopy = len; |
101 | } |
102 | len = length(); |
103 | _buffer = (char *)realloc(_buffer, len + numCopy + 1); |
104 | memcpy(_buffer + len, s, numCopy); |
105 | _buffer[len + numCopy] = '\0'; |
106 | } |
107 | return *this; |
108 | } |
109 | |
110 | String &String::append(FILE *fp, long filelen) { |
111 | int len = length(); |
112 | _buffer = (char *)realloc(_buffer, len + filelen + 1); |
113 | filelen = fread((void *)(len + _buffer), 1, filelen, fp); |
114 | _buffer[len + filelen] = 0; |
115 | return *this; |
116 | } |
117 | |
118 | void String::clear() { |
119 | free(_buffer); |
120 | _buffer = nullptr; |
121 | } |
122 | |
123 | bool String::equals(const String &s, bool ignoreCase) const { |
124 | bool result; |
125 | if (_buffer == s._buffer) { |
126 | result = true; |
127 | } else if (_buffer == nullptr || s._buffer == nullptr) { |
128 | result = _buffer == s._buffer; |
129 | } else if (ignoreCase) { |
130 | result = strcasecmp(_buffer, s._buffer) == 0; |
131 | } else { |
132 | result = strcmp(_buffer, s._buffer) == 0; |
133 | } |
134 | return result; |
135 | } |
136 | |
137 | bool String::equals(const char *s, bool ignoreCase) const { |
138 | return (_buffer == nullptr ? s == nullptr : |
139 | s == nullptr ? _buffer == nullptr : ignoreCase ? |
140 | strcasecmp(_buffer, s) == 0 : strcmp(_buffer, s) == 0); |
141 | } |
142 | |
143 | bool String::endsWith(const String &needle) const { |
144 | bool result; |
145 | int len1 = _buffer == nullptr ? 0 : strlen(_buffer); |
146 | int len2 = needle._buffer == nullptr ? 0 : strlen(needle._buffer); |
147 | if ((len1 == 0 || len2 == 0) || len2 > len1) { |
148 | // "cat" -> "cats" |
149 | result = false; |
150 | } else { |
151 | // "needle" -> "dle" |
152 | int fromIndex = len1 - len2; |
153 | result = (strcmp(_buffer + fromIndex, needle._buffer) == 0); |
154 | } |
155 | return result; |
156 | } |
157 | |
158 | int String::indexOf(const char *s, int fromIndex) const { |
159 | int result; |
160 | int len = length(); |
161 | if (fromIndex >= len || _buffer == nullptr) { |
162 | result = -1; |
163 | } else if (strlen(s) == 1) { |
164 | char *c = strchr(_buffer + fromIndex, s[0]); |
165 | result = (c == nullptr ? -1 : (c - _buffer)); |
166 | } else { |
167 | char *c = strstr(_buffer + fromIndex, s); |
168 | result = (c == nullptr ? -1 : (c - _buffer)); |
169 | } |
170 | return result; |
171 | } |
172 | |
173 | int String::indexOf(char chr, int fromIndex) const { |
174 | int len = length(); |
175 | if (fromIndex >= len) { |
176 | return -1; |
177 | } |
178 | char *c = strchr(_buffer + fromIndex, chr); |
179 | return (c == nullptr ? -1 : (c - _buffer)); |
180 | } |
181 | |
182 | int String::lastIndexOf(char chr, int untilIndex) const { |
183 | int len = length(); |
184 | if (untilIndex >= len || untilIndex < 0) { |
185 | return -1; |
186 | } |
187 | char *c = strrchr(_buffer + untilIndex, chr); |
188 | return (c == nullptr ? -1 : (c - _buffer)); |
189 | } |
190 | |
191 | String String::leftOf(char ch) const { |
192 | int endIndex = indexOf(ch, 0); |
193 | if (endIndex == -1) { |
194 | return *this; |
195 | } |
196 | return substring(0, endIndex); |
197 | } |
198 | |
199 | void String::replaceAll(char a, char b) { |
200 | int len = length(); |
201 | for (int i = 0; i < len; i++) { |
202 | if (_buffer[i] == a) { |
203 | _buffer[i] = b; |
204 | } |
205 | } |
206 | } |
207 | |
208 | String String::rightOf(char ch) const { |
209 | int endIndex = indexOf(ch, 0); |
210 | if (endIndex == -1) { |
211 | return *this; |
212 | } |
213 | return substring(endIndex + 1, length()); |
214 | } |
215 | |
216 | String String::substring(int beginIndex) const { |
217 | String out; |
218 | if (beginIndex < length()) { |
219 | out.append(_buffer + beginIndex); |
220 | } |
221 | return out; |
222 | } |
223 | |
224 | String String::substring(int beginIndex, int endIndex) const { |
225 | String out; |
226 | int len = length(); |
227 | if (endIndex > len) { |
228 | endIndex = len; |
229 | } |
230 | if (beginIndex < length()) { |
231 | out.append(_buffer + beginIndex, endIndex - beginIndex); |
232 | } |
233 | return out; |
234 | } |
235 | |
236 | void String::trim() { |
237 | int len = length(); |
238 | if (len == 0) { |
239 | return; |
240 | } |
241 | int ibegin = 0; |
242 | while (IS_WHITE(_buffer[ibegin])) { |
243 | ibegin++; |
244 | } |
245 | int iend = len; |
246 | while (IS_WHITE(_buffer[iend - 1])) { |
247 | iend--; |
248 | } |
249 | String s = substring(ibegin, iend); |
250 | clear(); |
251 | append(s); |
252 | } |
253 | |
254 | //--List------------------------------------------------------------------ |
255 | |
256 | template<> void List<String *>::add(const char *s) { |
257 | add(new String(s, strlen(s))); |
258 | } |
259 | |
260 | template<> bool List<String *>::contains(const char *s) { |
261 | bool result = false; |
262 | for (String **it = begin(); it != end(); it++) { |
263 | String *next = (*it); |
264 | if (next->equals(s)) { |
265 | result = true; |
266 | break; |
267 | } |
268 | } |
269 | return result; |
270 | } |
271 | |
272 | //--Properties------------------------------------------------------------------ |
273 | |
274 | template<> void Properties<String *>::load(const char *s) { |
275 | if (s && s[0]) { |
276 | load(s, strlen(s)); |
277 | } |
278 | } |
279 | |
280 | template<> void Properties<String *>::load(const char *s, int slen) { |
281 | if (s == 0 || s[0] == 0 || slen == 0) { |
282 | return; |
283 | } |
284 | |
285 | String attr; |
286 | String value; |
287 | |
288 | int i = 0; |
289 | while (i < slen) { |
290 | attr.clear(); |
291 | value.clear(); |
292 | |
293 | // remove w/s before attribute |
294 | while (i < slen && IS_WHITE(s[i])) { |
295 | i++; |
296 | } |
297 | if (i == slen) { |
298 | break; |
299 | } |
300 | int iBegin = i; |
301 | |
302 | // find end of attribute |
303 | while (i < slen && s[i] != '=' && !IS_WHITE(s[i])) { |
304 | i++; |
305 | } |
306 | if (i == slen) { |
307 | break; |
308 | } |
309 | |
310 | attr.append(s + iBegin, i - iBegin); |
311 | |
312 | // scan for equals |
313 | while (i < slen && IS_WHITE(s[i])) { |
314 | i++; |
315 | } |
316 | if (i == slen) { |
317 | break; |
318 | } |
319 | |
320 | if (s[i] != '=') { |
321 | break; |
322 | } |
323 | i++; // skip equals |
324 | |
325 | // scan value |
326 | while (i < slen && IS_WHITE(s[i])) { |
327 | i++; |
328 | } |
329 | if (i == slen) { |
330 | break; |
331 | } |
332 | |
333 | if (s[i] == '\"' || s[i] == '\'') { |
334 | // scan quoted value |
335 | char quote = s[i]; |
336 | iBegin = ++i; |
337 | while (i < slen && s[i] != quote) { |
338 | i++; |
339 | } |
340 | } else { |
341 | // non quoted value |
342 | iBegin = i; |
343 | while (i < slen && !IS_WHITE(s[i])) { |
344 | i++; |
345 | } |
346 | } |
347 | |
348 | value.append(s + iBegin, i - iBegin); |
349 | // append (put) to list |
350 | add(new String(attr)); |
351 | add(new String(value)); |
352 | i++; |
353 | } |
354 | } |
355 | |
356 | template<> void Properties<String *>::put(const char *key, const char *value) { |
357 | String *prev = get(key); |
358 | if (prev) { |
359 | prev->clear(); |
360 | prev->append(value); |
361 | } else { |
362 | add(new String(key)); |
363 | add(new String(value)); |
364 | } |
365 | } |
366 | |
367 | template<> void Properties<String *>::get(const char *key, List<String *> *arrayValues) { |
368 | for (int i = 0; i < _count; i++) { |
369 | String *nextKey = (String *)_head[i++]; |
370 | if (nextKey == nullptr || i == _count) { |
371 | break; |
372 | } |
373 | String *nextValue = (String *)_head[i]; |
374 | if (nextValue == nullptr) { |
375 | break; |
376 | } |
377 | if (nextKey->equals(key)) { |
378 | arrayValues->add(new String(*nextValue)); |
379 | } |
380 | } |
381 | } |
382 | |
383 | // g++ -DUNIT_TESTS=1 -I. ui/strlib.cpp && valgrind ./a.out |
384 | #if defined(UNIT_TESTS) |
385 | #include <stdio.h> |
386 | void assertEq(int a, int b) { |
387 | if (a != b) { |
388 | fprintf(stderr, "FAIL: %d != %d\n" , a, b); |
389 | } |
390 | } |
391 | int main() { |
392 | String s1 = "test string is here x" ; |
393 | String s2; |
394 | String s3 = "cats" ; |
395 | assertEq(0, s1.indexOf("t" , 0)); |
396 | assertEq(20, s1.indexOf("x" , 20)); |
397 | assertEq(5, s1.indexOf("string" , 4)); |
398 | assertEq(-1, s1.indexOf("not" , 10)); |
399 | assertEq(-1, s2.indexOf("not" , 10)); |
400 | assertEq(0, s3.equals(nullptr, true)); |
401 | assertEq(1, s3.equals("CATS" , true)); |
402 | assertEq(0, s3.equals("CATS" , false)); |
403 | assertEq(1, s3.equals("cats" , false)); |
404 | assertEq(1, s3.endsWith("ats" )); |
405 | assertEq(1, s3.endsWith("cats" )); |
406 | assertEq(0, s3.endsWith("morecats" )); |
407 | assertEq(1, s1.endsWith("x" )); |
408 | assertEq(1, s1.endsWith(" is here x" )); |
409 | assertEq(0, s1.endsWith(nullptr)); |
410 | assertEq('x', s1.lastChar()); |
411 | assertEq('\0', s2.lastChar()); |
412 | assertEq('s', s3.lastChar()); |
413 | return 0; |
414 | } |
415 | #endif |
416 | |