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
14using namespace strlib;
15
16//--String----------------------------------------------------------------------
17
18String::String() : _buffer(nullptr) {
19}
20
21String::String(const char *s) {
22 _buffer = (s == nullptr ? nullptr : strdup(s));
23}
24
25String::String(const String &s) {
26 _buffer = s._buffer == nullptr ? nullptr : strdup(s._buffer);
27}
28
29String::String(const char *s, int len) : _buffer(nullptr) {
30 append(s, len);
31}
32
33String::~String() {
34 free(_buffer);
35 _buffer = nullptr;
36}
37
38const 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
46const String &String::operator=(const char *s) {
47 clear();
48 if (_buffer != s) {
49 _buffer = strdup(s);
50 }
51 return *this;
52}
53
54const void String::operator+=(const String &s) {
55 append(s._buffer);
56}
57
58const void String::operator+=(const char *s) {
59 append(s);
60}
61
62String &String::append(const String &s) {
63 append(s._buffer);
64 return *this;
65}
66
67String &String::append(const String *s) {
68 if (s && !s->empty()) {
69 append(s->_buffer);
70 }
71 return *this;
72}
73
74String &String::append(int i) {
75 char t[20];
76 sprintf(t, "%i", i);
77 append(t);
78 return *this;
79}
80
81String &String::append(char c) {
82 char t[2] = { c, 0 };
83 append(t);
84 return *this;
85}
86
87String &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
96String &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
110String &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
118void String::clear() {
119 free(_buffer);
120 _buffer = nullptr;
121}
122
123bool 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
137bool 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
143bool 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
158int 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
173int 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
182int 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
191String 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
199void 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
208String 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
216String String::substring(int beginIndex) const {
217 String out;
218 if (beginIndex < length()) {
219 out.append(_buffer + beginIndex);
220 }
221 return out;
222}
223
224String 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
236void 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
256template<> void List<String *>::add(const char *s) {
257 add(new String(s, strlen(s)));
258}
259
260template<> 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
274template<> void Properties<String *>::load(const char *s) {
275 if (s && s[0]) {
276 load(s, strlen(s));
277 }
278}
279
280template<> 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
356template<> 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
367template<> 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>
386void assertEq(int a, int b) {
387 if (a != b) {
388 fprintf(stderr, "FAIL: %d != %d\n", a, b);
389 }
390}
391int 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