File: | String.c |
Warning: | line 473, column 23 Dereference of null pointer |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
1 | #include "String.h" | |||
2 | #include "Funcs.h" | |||
3 | #include "Logger.h" | |||
4 | #include "Platform.h" | |||
5 | #include "Stream.h" | |||
6 | #include "Utils.h" | |||
7 | ||||
8 | #ifdef __cplusplus | |||
9 | const cc_string String_Empty = { NULL((void*)0), 0, 0 }; | |||
10 | #else | |||
11 | const cc_string String_Empty; | |||
12 | #endif | |||
13 | ||||
14 | int String_CalcLen(const char* raw, int capacity) { | |||
15 | int length = 0; | |||
16 | while (length < capacity && *raw) { raw++; length++; } | |||
17 | return length; | |||
18 | } | |||
19 | ||||
20 | int String_Length(const char* raw) { | |||
21 | int length = 0; | |||
22 | while (length < UInt16_MaxValue((cc_uint16)65535) && *raw) { raw++; length++; } | |||
23 | return length; | |||
24 | } | |||
25 | ||||
26 | cc_string String_FromRaw(STRING_REF char* buffer, int capacity) { | |||
27 | return String_Init(buffer, String_CalcLen(buffer, capacity), capacity); | |||
28 | } | |||
29 | ||||
30 | cc_string String_FromReadonly(STRING_REF const char* buffer) { | |||
31 | int len = String_Length(buffer); | |||
32 | return String_Init((char*)buffer, len, len); | |||
33 | } | |||
34 | ||||
35 | ||||
36 | void String_Copy(cc_string* dst, const cc_string* src) { | |||
37 | dst->length = 0; | |||
38 | String_AppendString(dst, src); | |||
39 | } | |||
40 | ||||
41 | void String_CopyToRaw(char* dst, int capacity, const cc_string* src) { | |||
42 | int i, len = min(capacity, src->length)((capacity) < (src->length) ? (capacity) : (src->length )); | |||
43 | for (i = 0; i < len; i++) { dst[i] = src->buffer[i]; } | |||
44 | /* add \0 to mark end of used portion of buffer */ | |||
45 | if (len < capacity) dst[len] = '\0'; | |||
46 | } | |||
47 | ||||
48 | cc_string String_UNSAFE_Substring(STRING_REF const cc_string* str, int offset, int length) { | |||
49 | if (offset < 0 || offset > str->length) { | |||
50 | Logger_Abort("Offset for substring out of range"); | |||
51 | } | |||
52 | if (length < 0 || length > str->length) { | |||
53 | Logger_Abort("Length for substring out of range"); | |||
54 | } | |||
55 | if (offset + length > str->length) { | |||
56 | Logger_Abort("Result substring is out of range"); | |||
57 | } | |||
58 | return String_Init(str->buffer + offset, length, length); | |||
59 | } | |||
60 | ||||
61 | cc_string String_UNSAFE_SubstringAt(STRING_REF const cc_string* str, int offset) { | |||
62 | cc_string sub; | |||
63 | if (offset < 0 || offset > str->length) Logger_Abort("Sub offset out of range"); | |||
64 | ||||
65 | sub.buffer = str->buffer + offset; | |||
66 | sub.length = str->length - offset; | |||
67 | sub.capacity = str->length - offset; /* str->length to match String_UNSAFE_Substring */ | |||
68 | return sub; | |||
69 | } | |||
70 | ||||
71 | int String_UNSAFE_Split(STRING_REF const cc_string* str, char c, cc_string* subs, int maxSubs) { | |||
72 | int beg = 0, end, count, i; | |||
73 | ||||
74 | for (i = 0; i < maxSubs && beg <= str->length; i++) { | |||
75 | end = String_IndexOfAt(str, beg, c); | |||
76 | if (end == -1) end = str->length; | |||
77 | ||||
78 | subs[i] = String_UNSAFE_Substring(str, beg, end - beg); | |||
79 | beg = end + 1; | |||
80 | } | |||
81 | ||||
82 | count = i; | |||
83 | /* If not enough split substrings, make remaining NULL */ | |||
84 | for (; i < maxSubs; i++) { subs[i] = String_Empty; } | |||
85 | return count; | |||
86 | } | |||
87 | ||||
88 | void String_UNSAFE_SplitBy(STRING_REF cc_string* str, char c, cc_string* part) { | |||
89 | int idx = String_IndexOf(str, c)String_IndexOfAt(str, 0, c); | |||
90 | if (idx == -1) { | |||
91 | *part = *str; | |||
92 | *str = String_Empty; | |||
93 | } else { | |||
94 | *part = String_UNSAFE_Substring(str, 0, idx); idx++; | |||
95 | *str = String_UNSAFE_SubstringAt(str, idx); | |||
96 | } | |||
97 | } | |||
98 | ||||
99 | int String_UNSAFE_Separate(STRING_REF const cc_string* str, char c, cc_string* key, cc_string* value) { | |||
100 | int idx = String_IndexOf(str, c)String_IndexOfAt(str, 0, c); | |||
101 | if (idx == -1) { | |||
102 | *key = *str; | |||
103 | *value = String_Empty; | |||
104 | return false0; | |||
105 | } | |||
106 | ||||
107 | *key = String_UNSAFE_Substring(str, 0, idx); idx++; | |||
108 | *value = String_UNSAFE_SubstringAt(str, idx); | |||
109 | ||||
110 | /* Trim key [c] value to just key[c]value */ | |||
111 | String_UNSAFE_TrimEnd(key); | |||
112 | String_UNSAFE_TrimStart(value); | |||
113 | return key->length > 0 && value->length > 0; | |||
114 | } | |||
115 | ||||
116 | ||||
117 | int String_Equals(const cc_string* a, const cc_string* b) { | |||
118 | int i; | |||
119 | if (a->length != b->length) return false0; | |||
120 | ||||
121 | for (i = 0; i < a->length; i++) { | |||
122 | if (a->buffer[i] != b->buffer[i]) return false0; | |||
123 | } | |||
124 | return true1; | |||
125 | } | |||
126 | ||||
127 | int String_CaselessEquals(const cc_string* a, const cc_string* b) { | |||
128 | int i; | |||
129 | char aCur, bCur; | |||
130 | if (a->length != b->length) return false0; | |||
131 | ||||
132 | for (i = 0; i < a->length; i++) { | |||
133 | aCur = a->buffer[i]; Char_MakeLower(aCur)if ((aCur) >= 'A' && (aCur) <= 'Z') { (aCur) += ' '; }; | |||
134 | bCur = b->buffer[i]; Char_MakeLower(bCur)if ((bCur) >= 'A' && (bCur) <= 'Z') { (bCur) += ' '; }; | |||
135 | if (aCur != bCur) return false0; | |||
136 | } | |||
137 | return true1; | |||
138 | } | |||
139 | ||||
140 | int String_CaselessEqualsConst(const cc_string* a, const char* b) { | |||
141 | int i; | |||
142 | char aCur, bCur; | |||
143 | ||||
144 | for (i = 0; i < a->length; i++) { | |||
145 | aCur = a->buffer[i]; Char_MakeLower(aCur)if ((aCur) >= 'A' && (aCur) <= 'Z') { (aCur) += ' '; }; | |||
146 | bCur = b[i]; Char_MakeLower(bCur)if ((bCur) >= 'A' && (bCur) <= 'Z') { (bCur) += ' '; }; | |||
147 | if (aCur != bCur || bCur == '\0') return false0; | |||
148 | } | |||
149 | /* ensure at end of string */ | |||
150 | return b[a->length] == '\0'; | |||
151 | } | |||
152 | ||||
153 | ||||
154 | void String_Append(cc_string* str, char c) { | |||
155 | if (str->length == str->capacity) return; | |||
156 | str->buffer[str->length++] = c; | |||
157 | } | |||
158 | ||||
159 | void String_AppendBool(cc_string* str, cc_bool value) { | |||
160 | String_AppendConst(str, value ? "True" : "False"); | |||
161 | } | |||
162 | ||||
163 | int String_MakeUInt32(cc_uint32 num, char* digits) { | |||
164 | int len = 0; | |||
165 | do { | |||
166 | digits[len] = '0' + (num % 10); num /= 10; len++; | |||
167 | } while (num > 0); | |||
168 | return len; | |||
169 | } | |||
170 | ||||
171 | void String_AppendInt(cc_string* str, int num) { | |||
172 | if (num < 0) { | |||
173 | num = -num; | |||
174 | String_Append(str, '-'); | |||
175 | } | |||
176 | String_AppendUInt32(str, (cc_uint32)num); | |||
177 | } | |||
178 | ||||
179 | void String_AppendUInt32(cc_string* str, cc_uint32 num) { | |||
180 | char digits[STRING_INT_CHARS24]; | |||
181 | int i, count = String_MakeUInt32(num, digits); | |||
182 | ||||
183 | for (i = count - 1; i >= 0; i--) { | |||
184 | String_Append(str, digits[i]); | |||
185 | } | |||
186 | } | |||
187 | ||||
188 | void String_AppendPaddedInt(cc_string* str, int num, int minDigits) { | |||
189 | char digits[STRING_INT_CHARS24]; | |||
190 | int i, count; | |||
191 | for (i = 0; i < minDigits; i++) { digits[i] = '0'; } | |||
192 | ||||
193 | count = String_MakeUInt32(num, digits); | |||
194 | if (count < minDigits) count = minDigits; | |||
195 | ||||
196 | for (i = count - 1; i >= 0; i--) { | |||
197 | String_Append(str, digits[i]); | |||
198 | } | |||
199 | } | |||
200 | ||||
201 | void String_AppendFloat(cc_string* str, float num, int fracDigits) { | |||
202 | int i, whole, digit; | |||
203 | double frac; | |||
204 | ||||
205 | if (num < 0.0f) { | |||
206 | String_Append(str, '-'); /* don't need to check success */ | |||
207 | num = -num; | |||
208 | } | |||
209 | ||||
210 | whole = (int)num; | |||
211 | String_AppendUInt32(str, whole); | |||
212 | ||||
213 | frac = (double)num - (double)whole; | |||
214 | if (frac == 0.0) return; | |||
215 | String_Append(str, '.'); /* don't need to check success */ | |||
216 | ||||
217 | for (i = 0; i < fracDigits; i++) { | |||
218 | frac *= 10; | |||
219 | digit = (int)frac % 10; | |||
220 | String_Append(str, '0' + digit); | |||
221 | } | |||
222 | } | |||
223 | ||||
224 | void String_AppendHex(cc_string* str, cc_uint8 value) { | |||
225 | /* 48 = index of 0, 55 = index of (A - 10) */ | |||
226 | cc_uint8 hi = (value >> 4) & 0xF; | |||
227 | char c_hi = hi < 10 ? (hi + 48) : (hi + 55); | |||
228 | cc_uint8 lo = value & 0xF; | |||
229 | char c_lo = lo < 10 ? (lo + 48) : (lo + 55); | |||
230 | ||||
231 | String_Append(str, c_hi); | |||
232 | String_Append(str, c_lo); | |||
233 | } | |||
234 | ||||
235 | CC_NOINLINE__attribute__((noinline)) static void String_Hex32(cc_string* str, cc_uint32 value) { | |||
236 | int shift; | |||
237 | ||||
238 | for (shift = 24; shift >= 0; shift -= 8) { | |||
239 | cc_uint8 part = (cc_uint8)(value >> shift); | |||
240 | String_AppendHex(str, part); | |||
241 | } | |||
242 | } | |||
243 | ||||
244 | CC_NOINLINE__attribute__((noinline)) static void String_Hex64(cc_string* str, cc_uint64 value) { | |||
245 | int shift; | |||
246 | ||||
247 | for (shift = 56; shift >= 0; shift -= 8) { | |||
248 | cc_uint8 part = (cc_uint8)(value >> shift); | |||
249 | String_AppendHex(str, part); | |||
250 | } | |||
251 | } | |||
252 | ||||
253 | void String_AppendConst(cc_string* str, const char* src) { | |||
254 | for (; *src; src++) { | |||
255 | String_Append(str, *src); | |||
256 | } | |||
257 | } | |||
258 | ||||
259 | void String_AppendString(cc_string* str, const cc_string* src) { | |||
260 | int i; | |||
261 | for (i = 0; i < src->length; i++) { | |||
262 | String_Append(str, src->buffer[i]); | |||
263 | } | |||
264 | } | |||
265 | ||||
266 | void String_AppendColorless(cc_string* str, const cc_string* src) { | |||
267 | char c; | |||
268 | int i; | |||
269 | ||||
270 | for (i = 0; i < src->length; i++) { | |||
271 | c = src->buffer[i]; | |||
272 | if (c == '&') { i++; continue; } /* Skip over the following colour code */ | |||
273 | String_Append(str, c); | |||
274 | } | |||
275 | } | |||
276 | ||||
277 | ||||
278 | int String_IndexOfAt(const cc_string* str, int offset, char c) { | |||
279 | int i; | |||
280 | for (i = offset; i < str->length; i++) { | |||
281 | if (str->buffer[i] == c) return i; | |||
282 | } | |||
283 | return -1; | |||
284 | } | |||
285 | ||||
286 | int String_LastIndexOfAt(const cc_string* str, int offset, char c) { | |||
287 | int i; | |||
288 | for (i = (str->length - 1) - offset; i >= 0; i--) { | |||
289 | if (str->buffer[i] == c) return i; | |||
290 | } | |||
291 | return -1; | |||
292 | } | |||
293 | ||||
294 | int String_NthIndexOfFromRight(const cc_string* str, char c, int number) { | |||
295 | int nth = 1; | |||
296 | int i; | |||
297 | for (i = (str->length - 1); i >= 0; i--) { | |||
298 | if (str->buffer[i] == c) { | |||
299 | if (number == nth) { | |||
300 | return i; | |||
301 | } | |||
302 | else { | |||
303 | nth++; | |||
304 | } | |||
305 | } | |||
306 | } | |||
307 | return -1; | |||
308 | } | |||
309 | ||||
310 | void String_InsertAt(cc_string* str, int offset, char c) { | |||
311 | int i; | |||
312 | ||||
313 | if (offset < 0 || offset > str->length) { | |||
314 | Logger_Abort("Offset for InsertAt out of range"); | |||
315 | } | |||
316 | if (str->length == str->capacity) { | |||
317 | Logger_Abort("Cannot insert character into full string"); | |||
318 | } | |||
319 | ||||
320 | for (i = str->length; i > offset; i--) { | |||
321 | str->buffer[i] = str->buffer[i - 1]; | |||
322 | } | |||
323 | str->buffer[offset] = c; | |||
324 | str->length++; | |||
325 | } | |||
326 | ||||
327 | void String_DeleteAt(cc_string* str, int offset) { | |||
328 | int i; | |||
329 | ||||
330 | if (offset < 0 || offset >= str->length) { | |||
331 | Logger_Abort("Offset for DeleteAt out of range"); | |||
332 | } | |||
333 | ||||
334 | for (i = offset; i < str->length - 1; i++) { | |||
335 | str->buffer[i] = str->buffer[i + 1]; | |||
336 | } | |||
337 | str->buffer[str->length - 1] = '\0'; | |||
338 | str->length--; | |||
339 | } | |||
340 | ||||
341 | void String_UNSAFE_TrimStart(cc_string* str) { | |||
342 | int i; | |||
343 | for (i = 0; i < str->length; i++) { | |||
344 | if (str->buffer[i] != ' ') break; | |||
345 | ||||
346 | str->buffer++; | |||
347 | str->length--; i--; | |||
348 | } | |||
349 | } | |||
350 | ||||
351 | void String_UNSAFE_TrimEnd(cc_string* str) { | |||
352 | int i; | |||
353 | for (i = str->length - 1; i >= 0; i--) { | |||
354 | if (str->buffer[i] != ' ') break; | |||
355 | str->length--; | |||
356 | } | |||
357 | } | |||
358 | ||||
359 | int String_IndexOfConst(const cc_string* str, const char* sub) { | |||
360 | int i, j; | |||
361 | ||||
362 | for (i = 0; i < str->length; i++) { | |||
363 | for (j = 0; sub[j] && (i + j) < str->length; j++) { | |||
364 | ||||
365 | if (str->buffer[i + j] != sub[j]) break; | |||
366 | } | |||
367 | if (sub[j] == '\0') return i; | |||
368 | } | |||
369 | return -1; | |||
370 | } | |||
371 | ||||
372 | int String_CaselessContains(const cc_string* str, const cc_string* sub) { | |||
373 | char strCur, subCur; | |||
374 | int i, j; | |||
375 | ||||
376 | for (i = 0; i < str->length; i++) { | |||
377 | for (j = 0; j < sub->length && (i + j) < str->length; j++) { | |||
378 | ||||
379 | strCur = str->buffer[i + j]; Char_MakeLower(strCur)if ((strCur) >= 'A' && (strCur) <= 'Z') { (strCur ) += ' '; }; | |||
380 | subCur = sub->buffer[j]; Char_MakeLower(subCur)if ((subCur) >= 'A' && (subCur) <= 'Z') { (subCur ) += ' '; }; | |||
381 | if (strCur != subCur) break; | |||
382 | } | |||
383 | if (j == sub->length) return true1; | |||
384 | } | |||
385 | return false0; | |||
386 | } | |||
387 | ||||
388 | int String_CaselessStarts(const cc_string* str, const cc_string* sub) { | |||
389 | char strCur, subCur; | |||
390 | int i; | |||
391 | if (str->length < sub->length) return false0; | |||
392 | ||||
393 | for (i = 0; i < sub->length; i++) { | |||
394 | strCur = str->buffer[i]; Char_MakeLower(strCur)if ((strCur) >= 'A' && (strCur) <= 'Z') { (strCur ) += ' '; }; | |||
395 | subCur = sub->buffer[i]; Char_MakeLower(subCur)if ((subCur) >= 'A' && (subCur) <= 'Z') { (subCur ) += ' '; }; | |||
396 | if (strCur != subCur) return false0; | |||
397 | } | |||
398 | return true1; | |||
399 | } | |||
400 | ||||
401 | int String_CaselessEnds(const cc_string* str, const cc_string* sub) { | |||
402 | char strCur, subCur; | |||
403 | int i, j = str->length - sub->length; | |||
404 | if (j < 0) return false0; /* sub longer than str */ | |||
405 | ||||
406 | for (i = 0; i < sub->length; i++) { | |||
407 | strCur = str->buffer[j + i]; Char_MakeLower(strCur)if ((strCur) >= 'A' && (strCur) <= 'Z') { (strCur ) += ' '; }; | |||
408 | subCur = sub->buffer[i]; Char_MakeLower(subCur)if ((subCur) >= 'A' && (subCur) <= 'Z') { (subCur ) += ' '; }; | |||
409 | if (strCur != subCur) return false0; | |||
410 | } | |||
411 | return true1; | |||
412 | } | |||
413 | ||||
414 | int String_Compare(const cc_string* a, const cc_string* b) { | |||
415 | char aCur, bCur; | |||
416 | int i, minLen = min(a->length, b->length)((a->length) < (b->length) ? (a->length) : (b-> length)); | |||
417 | ||||
418 | for (i = 0; i < minLen; i++) { | |||
419 | aCur = a->buffer[i]; Char_MakeLower(aCur)if ((aCur) >= 'A' && (aCur) <= 'Z') { (aCur) += ' '; }; | |||
420 | bCur = b->buffer[i]; Char_MakeLower(bCur)if ((bCur) >= 'A' && (bCur) <= 'Z') { (bCur) += ' '; }; | |||
421 | ||||
422 | if (aCur == bCur) continue; | |||
423 | return (cc_uint8)aCur - (cc_uint8)bCur; | |||
424 | } | |||
425 | ||||
426 | /* all chars are equal here - same string, or a substring */ | |||
427 | return a->length - b->length; | |||
428 | } | |||
429 | ||||
430 | void String_Format1(cc_string* str, const char* format, const void* a1) { | |||
431 | String_Format4(str, format, a1, NULL((void*)0), NULL((void*)0), NULL((void*)0)); | |||
| ||||
432 | } | |||
433 | void String_Format2(cc_string* str, const char* format, const void* a1, const void* a2) { | |||
434 | String_Format4(str, format, a1, a2, NULL((void*)0), NULL((void*)0)); | |||
435 | } | |||
436 | void String_Format3(cc_string* str, const char* format, const void* a1, const void* a2, const void* a3) { | |||
437 | String_Format4(str, format, a1, a2, a3, NULL((void*)0)); | |||
438 | } | |||
439 | void String_Format4(cc_string* str, const char* format, const void* a1, const void* a2, const void* a3, const void* a4) { | |||
440 | const void* arg; | |||
441 | int i, j = 0, digits; | |||
442 | ||||
443 | const void* args[4]; | |||
444 | args[0] = a1; args[1] = a2; args[2] = a3; args[3] = a4; | |||
445 | ||||
446 | for (i = 0; format[i]; i++) { | |||
447 | if (format[i] != '%') { String_Append(str, format[i]); continue; } | |||
448 | arg = args[j++]; | |||
449 | ||||
450 | switch (format[++i]) { | |||
451 | case 'b': | |||
452 | String_AppendInt(str, *((cc_uint8*)arg)); break; | |||
453 | case 'i': | |||
454 | String_AppendInt(str, *((int*)arg)); break; | |||
455 | case 'f': | |||
456 | digits = format[++i] - '0'; | |||
457 | String_AppendFloat(str, *((float*)arg), digits); break; | |||
458 | case 'p': | |||
459 | digits = format[++i] - '0'; | |||
460 | String_AppendPaddedInt(str, *((int*)arg), digits); break; | |||
461 | case 't': | |||
462 | String_AppendBool(str, *((cc_bool*)arg)); break; | |||
463 | case 'c': | |||
464 | String_AppendConst(str, (char*)arg); break; | |||
465 | case 's': | |||
466 | String_AppendString(str, (cc_string*)arg); break; | |||
467 | case 'r': | |||
468 | String_Append(str, *((char*)arg)); break; | |||
469 | case 'x': | |||
470 | if (sizeof(cc_uintptr) == 4) { | |||
471 | String_Hex32(str, *((cc_uint32*)arg)); break; | |||
472 | } else { | |||
473 | String_Hex64(str, *((cc_uint64*)arg)); break; | |||
| ||||
474 | } | |||
475 | case 'h': | |||
476 | String_Hex32(str, *((cc_uint32*)arg)); break; | |||
477 | case '%': | |||
478 | String_Append(str, '%'); break; | |||
479 | default: | |||
480 | Logger_Abort("Invalid type for string format"); | |||
481 | } | |||
482 | } | |||
483 | } | |||
484 | ||||
485 | ||||
486 | /*########################################################################################################################* | |||
487 | *------------------------------------------------Character set conversions------------------------------------------------* | |||
488 | *#########################################################################################################################*/ | |||
489 | static const cc_unichar controlChars[32] = { | |||
490 | 0x0000, 0x263A, 0x263B, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022, | |||
491 | 0x25D8, 0x25CB, 0x25D9, 0x2642, 0x2640, 0x266A, 0x266B, 0x263C, | |||
492 | 0x25BA, 0x25C4, 0x2195, 0x203C, 0x00B6, 0x00A7, 0x25AC, 0x21A8, | |||
493 | 0x2191, 0x2193, 0x2192, 0x2190, 0x221F, 0x2194, 0x25B2, 0x25BC | |||
494 | }; | |||
495 | ||||
496 | static const cc_unichar extendedChars[129] = { 0x2302, | |||
497 | 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7, | |||
498 | 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5, | |||
499 | 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9, | |||
500 | 0x00FF, 0x00D6, 0x00DC, 0x00A2, 0x00A3, 0x00A5, 0x20A7, 0x0192, | |||
501 | 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA, | |||
502 | 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB, | |||
503 | 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, | |||
504 | 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510, | |||
505 | 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, | |||
506 | 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567, | |||
507 | 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, | |||
508 | 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580, | |||
509 | 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4, | |||
510 | 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229, | |||
511 | 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248, | |||
512 | 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0 | |||
513 | }; | |||
514 | ||||
515 | cc_unichar Convert_CP437ToUnicode(char c) { | |||
516 | cc_uint8 raw = (cc_uint8)c; | |||
517 | if (raw < 0x20) return controlChars[raw]; | |||
518 | if (raw < 0x7F) return raw; | |||
519 | return extendedChars[raw - 0x7F]; | |||
520 | } | |||
521 | ||||
522 | char Convert_CodepointToCP437(cc_codepoint cp) { | |||
523 | char c; Convert_TryCodepointToCP437(cp, &c); return c; | |||
524 | } | |||
525 | ||||
526 | static cc_codepoint ReduceEmoji(cc_codepoint cp) { | |||
527 | if (cp == 0x1F31E) return 0x263C; | |||
528 | if (cp == 0x1F3B5) return 0x266B; | |||
529 | if (cp == 0x1F642) return 0x263A; | |||
530 | ||||
531 | if (cp == 0x1F600 || cp == 0x1F601 || cp == 0x1F603) return 0x263A; | |||
532 | if (cp == 0x1F604 || cp == 0x1F606 || cp == 0x1F60A) return 0x263A; | |||
533 | return cp; | |||
534 | } | |||
535 | ||||
536 | cc_bool Convert_TryCodepointToCP437(cc_codepoint cp, char* c) { | |||
537 | int i; | |||
538 | if (cp >= 0x20 && cp < 0x7F) { *c = (char)cp; return true1; } | |||
539 | if (cp >= 0x1F000) cp = ReduceEmoji(cp); | |||
540 | ||||
541 | for (i = 0; i < Array_Elems(controlChars)(sizeof(controlChars) / sizeof(controlChars[0])); i++) { | |||
542 | if (controlChars[i] == cp) { *c = i; return true1; } | |||
543 | } | |||
544 | for (i = 0; i < Array_Elems(extendedChars)(sizeof(extendedChars) / sizeof(extendedChars[0])); i++) { | |||
545 | if (extendedChars[i] == cp) { *c = i + 0x7F; return true1; } | |||
546 | } | |||
547 | ||||
548 | *c = '?'; return false0; | |||
549 | } | |||
550 | ||||
551 | int Convert_Utf8ToCodepoint(cc_codepoint* cp, const cc_uint8* data, cc_uint32 len) { | |||
552 | *cp = '\0'; | |||
553 | if (!len) return 0; | |||
554 | ||||
555 | if (data[0] <= 0x7F) { | |||
556 | *cp = data[0]; | |||
557 | return 1; | |||
558 | } else if ((data[0] & 0xE0) == 0xC0) { | |||
559 | if (len < 2) return 0; | |||
560 | ||||
561 | *cp = ((data[0] & 0x1F) << 6) | ((data[1] & 0x3F)); | |||
562 | return 2; | |||
563 | } else if ((data[0] & 0xF0) == 0xE0) { | |||
564 | if (len < 3) return 0; | |||
565 | ||||
566 | *cp = ((data[0] & 0x0F) << 12) | ((data[1] & 0x3F) << 6) | |||
567 | | ((data[2] & 0x3F)); | |||
568 | return 3; | |||
569 | } else { | |||
570 | if (len < 4) return 0; | |||
571 | ||||
572 | *cp = ((data[0] & 0x07) << 18) | ((data[1] & 0x3F) << 12) | |||
573 | | ((data[2] & 0x3F) << 6) | (data[3] & 0x3F); | |||
574 | return 4; | |||
575 | } | |||
576 | } | |||
577 | ||||
578 | /* Encodes a unicode character in UTF8, returning number of bytes written */ | |||
579 | static int Convert_UnicodeToUtf8(cc_unichar uc, cc_uint8* data) { | |||
580 | if (uc <= 0x7F) { | |||
581 | data[0] = (cc_uint8)uc; | |||
582 | return 1; | |||
583 | } else if (uc <= 0x7FF) { | |||
584 | data[0] = 0xC0 | ((uc >> 6) & 0x1F); | |||
585 | data[1] = 0x80 | ((uc) & 0x3F); | |||
586 | return 2; | |||
587 | } else { | |||
588 | data[0] = 0xE0 | ((uc >> 12) & 0x0F); | |||
589 | data[1] = 0x80 | ((uc >> 6) & 0x3F); | |||
590 | data[2] = 0x80 | ((uc) & 0x3F); | |||
591 | return 3; | |||
592 | } | |||
593 | } | |||
594 | ||||
595 | int Convert_CP437ToUtf8(char c, cc_uint8* data) { | |||
596 | /* Common ASCII case */ | |||
597 | if (c >= 0x20 && c < 0x7F) { | |||
598 | data[0] = (cc_uint8)c; | |||
599 | return 1; | |||
600 | } | |||
601 | return Convert_UnicodeToUtf8(Convert_CP437ToUnicode(c), data); | |||
602 | } | |||
603 | ||||
604 | void String_AppendUtf16(cc_string* value, const cc_unichar* chars, int numBytes) { | |||
605 | int i; char c; | |||
606 | ||||
607 | for (i = 0; i < (numBytes >> 1); i++) { | |||
608 | /* TODO: UTF16 to codepoint conversion */ | |||
609 | if (Convert_TryCodepointToCP437(chars[i], &c)) String_Append(value, c); | |||
610 | } | |||
611 | } | |||
612 | ||||
613 | void String_AppendUtf8(cc_string* value, const cc_uint8* chars, int numBytes) { | |||
614 | int len; cc_codepoint cp; char c; | |||
615 | ||||
616 | for (; numBytes > 0; numBytes -= len) { | |||
617 | len = Convert_Utf8ToCodepoint(&cp, chars, numBytes); | |||
618 | if (!len) return; | |||
619 | ||||
620 | if (Convert_TryCodepointToCP437(cp, &c)) String_Append(value, c); | |||
621 | chars += len; | |||
622 | } | |||
623 | } | |||
624 | ||||
625 | void String_DecodeCP1252(cc_string* value, const cc_uint8* chars, int numBytes) { | |||
626 | int i; char c; | |||
627 | ||||
628 | for (i = 0; i < numBytes; i++) { | |||
629 | if (Convert_TryCodepointToCP437(chars[i], &c)) String_Append(value, c); | |||
630 | } | |||
631 | } | |||
632 | ||||
633 | ||||
634 | /*########################################################################################################################* | |||
635 | *--------------------------------------------------Numerical conversions--------------------------------------------------* | |||
636 | *#########################################################################################################################*/ | |||
637 | cc_bool Convert_ParseUInt8(const cc_string* str, cc_uint8* value) { | |||
638 | int tmp; | |||
639 | *value = 0; | |||
640 | if (!Convert_ParseInt(str, &tmp) || tmp < 0 || tmp > UInt8_MaxValue((cc_uint8)255)) return false0; | |||
641 | *value = (cc_uint8)tmp; return true1; | |||
642 | } | |||
643 | ||||
644 | cc_bool Convert_ParseUInt16(const cc_string* str, cc_uint16* value) { | |||
645 | int tmp; | |||
646 | *value = 0; | |||
647 | if (!Convert_ParseInt(str, &tmp) || tmp < 0 || tmp > UInt16_MaxValue((cc_uint16)65535)) return false0; | |||
648 | *value = (cc_uint16)tmp; return true1; | |||
649 | } | |||
650 | ||||
651 | static int Convert_CompareDigits(const char* digits, const char* magnitude) { | |||
652 | int i; | |||
653 | for (i = 0; ; i++) { | |||
654 | if (magnitude[i] == '\0') return 0; | |||
655 | if (digits[i] > magnitude[i]) return 1; | |||
656 | if (digits[i] < magnitude[i]) return -1; | |||
657 | } | |||
658 | return 0; | |||
659 | } | |||
660 | ||||
661 | static cc_bool Convert_TryParseDigits(const cc_string* str, cc_bool* negative, char* digits, int maxDigits) { | |||
662 | char* start = digits; | |||
663 | int offset = 0, i; | |||
664 | ||||
665 | *negative = false0; | |||
666 | if (!str->length) return false0; | |||
667 | digits += (maxDigits - 1); | |||
668 | ||||
669 | /* Handle number signs */ | |||
670 | if (str->buffer[0] == '-') { *negative = true1; offset = 1; } | |||
671 | if (str->buffer[0] == '+') { offset = 1; } | |||
672 | ||||
673 | /* add digits, starting at last digit */ | |||
674 | for (i = str->length - 1; i >= offset; i--) { | |||
675 | char c = str->buffer[i]; | |||
676 | if (c < '0' || c > '9' || digits < start) return false0; | |||
677 | *digits-- = c; | |||
678 | } | |||
679 | ||||
680 | for (; digits >= start; ) { *digits-- = '0'; } | |||
681 | return true1; | |||
682 | } | |||
683 | ||||
684 | #define INT32_DIGITS10 10 | |||
685 | cc_bool Convert_ParseInt(const cc_string* str, int* value) { | |||
686 | cc_bool negative; | |||
687 | char digits[INT32_DIGITS10]; | |||
688 | int i, compare, sum = 0; | |||
689 | ||||
690 | *value = 0; | |||
691 | if (!Convert_TryParseDigits(str, &negative, digits, INT32_DIGITS10)) return false0; | |||
692 | ||||
693 | if (negative) { | |||
694 | compare = Convert_CompareDigits(digits, "2147483648"); | |||
695 | /* Special case, since |largest min value| is > |largest max value| */ | |||
696 | if (compare == 0) { *value = Int32_MinValue((cc_int32)-2147483647L - (cc_int32)1L); return true1; } | |||
697 | } else { | |||
698 | compare = Convert_CompareDigits(digits, "2147483647"); | |||
699 | } | |||
700 | if (compare > 0) return false0; | |||
701 | ||||
702 | for (i = 0; i < INT32_DIGITS10; i++) { | |||
703 | sum *= 10; sum += digits[i] - '0'; | |||
704 | } | |||
705 | ||||
706 | if (negative) sum = -sum; | |||
707 | *value = sum; | |||
708 | return true1; | |||
709 | } | |||
710 | ||||
711 | #define UINT64_DIGITS20 20 | |||
712 | cc_bool Convert_ParseUInt64(const cc_string* str, cc_uint64* value) { | |||
713 | cc_bool negative; | |||
714 | char digits[UINT64_DIGITS20]; | |||
715 | int i, compare; | |||
716 | cc_uint64 sum = 0; | |||
717 | ||||
718 | *value = 0; | |||
719 | if (!Convert_TryParseDigits(str, &negative, digits, UINT64_DIGITS20)) return false0; | |||
720 | ||||
721 | compare = Convert_CompareDigits(digits, "18446744073709551615"); | |||
722 | if (negative || compare > 0) return false0; | |||
723 | ||||
724 | for (i = 0; i < UINT64_DIGITS20; i++) { | |||
725 | sum *= 10; sum += digits[i] - '0'; | |||
726 | } | |||
727 | ||||
728 | *value = sum; | |||
729 | return true1; | |||
730 | } | |||
731 | ||||
732 | cc_bool Convert_ParseFloat(const cc_string* str, float* value) { | |||
733 | int i = 0; | |||
734 | cc_bool negate = false0; | |||
735 | double sum, whole, fract, divide = 10.0; | |||
736 | *value = 0.0f; | |||
737 | ||||
738 | /* Handle number signs */ | |||
739 | if (!str->length) return false0; | |||
740 | if (str->buffer[0] == '-') { negate = true1; i++; } | |||
741 | if (str->buffer[0] == '+') { i++; } | |||
742 | ||||
743 | for (whole = 0.0; i < str->length; i++) { | |||
744 | char c = str->buffer[i]; | |||
745 | if (c == '.' || c == ',') { i++; break; } | |||
746 | ||||
747 | if (c < '0' || c > '9') return false0; | |||
748 | whole *= 10; whole += (c - '0'); | |||
749 | } | |||
750 | ||||
751 | for (fract = 0.0; i < str->length; i++) { | |||
752 | char c = str->buffer[i]; | |||
753 | if (c < '0' || c > '9') return false0; | |||
754 | ||||
755 | fract += (c - '0') / divide; divide *= 10; | |||
756 | } | |||
757 | ||||
758 | sum = whole + fract; | |||
759 | if (negate) sum = -sum; | |||
760 | *value = (float)sum; | |||
761 | return true1; | |||
762 | } | |||
763 | ||||
764 | cc_bool Convert_ParseBool(const cc_string* str, cc_bool* value) { | |||
765 | if (String_CaselessEqualsConst(str, "True")) { | |||
766 | *value = true1; return true1; | |||
767 | } | |||
768 | if (String_CaselessEqualsConst(str, "False")) { | |||
769 | *value = false0; return true1; | |||
770 | } | |||
771 | *value = false0; return false0; | |||
772 | } | |||
773 | ||||
774 | ||||
775 | /*########################################################################################################################* | |||
776 | *------------------------------------------------------StringsBuffer------------------------------------------------------* | |||
777 | *#########################################################################################################################*/ | |||
778 | #define STRINGSBUFFER_BUFFER_EXPAND_SIZE8192 8192 | |||
779 | ||||
780 | CC_NOINLINE__attribute__((noinline)) static void StringsBuffer_Init(struct StringsBuffer* buffer) { | |||
781 | buffer->count = 0; | |||
782 | buffer->totalLength = 0; | |||
783 | buffer->textBuffer = buffer->_defaultBuffer; | |||
784 | buffer->flagsBuffer = buffer->_defaultFlags; | |||
785 | buffer->_textCapacity = STRINGSBUFFER_BUFFER_DEF_SIZE4096; | |||
786 | buffer->_flagsCapacity = STRINGSBUFFER_FLAGS_DEF_ELEMS256; | |||
787 | } | |||
788 | ||||
789 | void StringsBuffer_Clear(struct StringsBuffer* buffer) { | |||
790 | /* Never initialised to begin with */ | |||
791 | if (!buffer->_flagsCapacity) return; | |||
792 | ||||
793 | if (buffer->textBuffer != buffer->_defaultBuffer) { | |||
794 | Mem_Free(buffer->textBuffer); | |||
795 | } | |||
796 | if (buffer->flagsBuffer != buffer->_defaultFlags) { | |||
797 | Mem_Free(buffer->flagsBuffer); | |||
798 | } | |||
799 | StringsBuffer_Init(buffer); | |||
800 | } | |||
801 | ||||
802 | cc_string StringsBuffer_UNSAFE_Get(struct StringsBuffer* buffer, int i) { | |||
803 | cc_uint32 flags, offset, len; | |||
804 | if (i < 0 || i >= buffer->count) Logger_Abort("Tried to get String past StringsBuffer end"); | |||
805 | ||||
806 | flags = buffer->flagsBuffer[i]; | |||
807 | offset = flags >> STRINGSBUFFER_LEN_SHIFT9; | |||
808 | len = flags & STRINGSBUFFER_LEN_MASK0x1FFUL; | |||
809 | return String_Init(&buffer->textBuffer[offset], len, len); | |||
810 | } | |||
811 | ||||
812 | void StringsBuffer_Add(struct StringsBuffer* buffer, const cc_string* str) { | |||
813 | int textOffset; | |||
814 | /* StringsBuffer hasn't been initialised yet, do it here */ | |||
815 | if (!buffer->_flagsCapacity) StringsBuffer_Init(buffer); | |||
816 | ||||
817 | if (buffer->count == buffer->_flagsCapacity) { | |||
818 | Utils_Resize((void**)&buffer->flagsBuffer, &buffer->_flagsCapacity, | |||
819 | 4, STRINGSBUFFER_FLAGS_DEF_ELEMS256, 512); | |||
820 | } | |||
821 | ||||
822 | if (str->length > STRINGSBUFFER_LEN_MASK0x1FFUL) { | |||
823 | Logger_Abort("String too big to insert into StringsBuffer"); | |||
824 | } | |||
825 | ||||
826 | textOffset = buffer->totalLength; | |||
827 | if (textOffset + str->length >= buffer->_textCapacity) { | |||
828 | Utils_Resize((void**)&buffer->textBuffer, &buffer->_textCapacity, | |||
829 | 1, STRINGSBUFFER_BUFFER_DEF_SIZE4096, 8192); | |||
830 | } | |||
831 | ||||
832 | Mem_Copy(&buffer->textBuffer[textOffset], str->buffer, str->length); | |||
833 | buffer->flagsBuffer[buffer->count] = str->length | (textOffset << STRINGSBUFFER_LEN_SHIFT9); | |||
834 | ||||
835 | buffer->count++; | |||
836 | buffer->totalLength += str->length; | |||
837 | } | |||
838 | ||||
839 | void StringsBuffer_Remove(struct StringsBuffer* buffer, int index) { | |||
840 | cc_uint32 flags, offset, len; | |||
841 | cc_uint32 i, offsetAdj; | |||
842 | if (index < 0 || index >= buffer->count) Logger_Abort("Tried to remove String past StringsBuffer end"); | |||
843 | ||||
844 | flags = buffer->flagsBuffer[index]; | |||
845 | offset = flags >> STRINGSBUFFER_LEN_SHIFT9; | |||
846 | len = flags & STRINGSBUFFER_LEN_MASK0x1FFUL; | |||
847 | ||||
848 | /* Imagine buffer is this: AAXXYYZZ, and want to delete XX */ | |||
849 | /* We iterate from first char of Y to last char of Z, */ | |||
850 | /* shifting each character two to the left. */ | |||
851 | for (i = offset + len; i < buffer->totalLength; i++) { | |||
852 | buffer->textBuffer[i - len] = buffer->textBuffer[i]; | |||
853 | } | |||
854 | ||||
855 | /* Adjust text offset of elements after this element */ | |||
856 | /* Elements may not be in order so must account for that */ | |||
857 | offsetAdj = len << STRINGSBUFFER_LEN_SHIFT9; | |||
858 | for (i = index; i < buffer->count - 1; i++) { | |||
859 | buffer->flagsBuffer[i] = buffer->flagsBuffer[i + 1]; | |||
860 | if (buffer->flagsBuffer[i] >= flags) { | |||
861 | buffer->flagsBuffer[i] -= offsetAdj; | |||
862 | } | |||
863 | } | |||
864 | ||||
865 | buffer->count--; | |||
866 | buffer->totalLength -= len; | |||
867 | } | |||
868 | ||||
869 | ||||
870 | /*########################################################################################################################* | |||
871 | *------------------------------------------------------Word wrapper-------------------------------------------------------* | |||
872 | *#########################################################################################################################*/ | |||
873 | static cc_bool WordWrap_IsWrapper(char c) { | |||
874 | return c == '\0' || c == ' ' || c == '-' || c == '>' || c == '<' || c == '/' || c == '\\'; | |||
875 | } | |||
876 | ||||
877 | void WordWrap_Do(STRING_REF cc_string* text, cc_string* lines, int numLines, int lineLen) { | |||
878 | int i, lineStart, lineEnd; | |||
879 | for (i = 0; i < numLines; i++) { lines[i] = String_Empty; } | |||
880 | ||||
881 | for (i = 0, lineStart = 0; i < numLines; i++) { | |||
882 | int nextLineStart = lineStart + lineLen; | |||
883 | /* No more text to wrap */ | |||
884 | if (nextLineStart >= text->length) { | |||
885 | lines[i] = String_UNSAFE_SubstringAt(text, lineStart); return; | |||
886 | } | |||
887 | ||||
888 | /* Find beginning of last word on current line */ | |||
889 | for (lineEnd = nextLineStart; lineEnd >= lineStart; lineEnd--) { | |||
890 | if (WordWrap_IsWrapper(text->buffer[lineEnd])) break; | |||
891 | } | |||
892 | lineEnd++; /* move after wrapper char (i.e. beginning of last word) */ | |||
893 | ||||
894 | if (lineEnd <= lineStart || lineEnd >= nextLineStart) { | |||
895 | /* Three special cases handled by this: */ | |||
896 | /* - Entire line is filled with a single word */ | |||
897 | /* - Last character(s) on current line are wrapper characters */ | |||
898 | /* - First character on next line is a wrapper character (last word ends at current line end) */ | |||
899 | lines[i] = String_UNSAFE_Substring(text, lineStart, lineLen); | |||
900 | lineStart += lineLen; | |||
901 | } else { | |||
902 | /* Last word in current line does not end in current line (extends onto next line) */ | |||
903 | /* Trim current line to end at beginning of last word */ | |||
904 | /* Set next line to start at beginning of last word */ | |||
905 | lines[i] = String_UNSAFE_Substring(text, lineStart, lineEnd - lineStart); | |||
906 | lineStart = lineEnd; | |||
907 | } | |||
908 | } | |||
909 | } | |||
910 | ||||
911 | void WordWrap_GetCoords(int index, const cc_string* lines, int numLines, int* coordX, int* coordY) { | |||
912 | int y, offset = 0, lineLen; | |||
913 | if (index == -1) index = Int32_MaxValue((cc_int32)2147483647L); | |||
914 | *coordX = -1; *coordY = 0; | |||
915 | ||||
916 | for (y = 0; y < numLines; y++) { | |||
917 | lineLen = lines[y].length; | |||
918 | if (!lineLen) break; | |||
919 | ||||
920 | *coordY = y; | |||
921 | if (index < offset + lineLen) { | |||
922 | *coordX = index - offset; break; | |||
923 | } | |||
924 | offset += lineLen; | |||
925 | } | |||
926 | if (*coordX == -1) *coordX = lines[*coordY].length; | |||
927 | } | |||
928 | ||||
929 | int WordWrap_GetBackLength(const cc_string* text, int index) { | |||
930 | int start = index; | |||
931 | if (index <= 0) return 0; | |||
932 | if (index >= text->length) Logger_Abort("WordWrap_GetBackLength - index past end of string"); | |||
933 | ||||
934 | /* Go backward to the end of the previous word */ | |||
935 | while (index > 0 && text->buffer[index] == ' ') index--; | |||
936 | /* Go backward to the start of the current word */ | |||
937 | while (index > 0 && text->buffer[index] != ' ') index--; | |||
938 | ||||
939 | return start - index; | |||
940 | } | |||
941 | ||||
942 | int WordWrap_GetForwardLength(const cc_string* text, int index) { | |||
943 | int start = index, length = text->length; | |||
944 | if (index == -1) return 0; | |||
945 | if (index >= text->length) Logger_Abort("WordWrap_GetForwardLength - index past end of string"); | |||
946 | ||||
947 | /* Go forward to the end of the word 'index' is currently in */ | |||
948 | while (index < length && text->buffer[index] != ' ') index++; | |||
949 | /* Go forward to the start of the next word after 'index' */ | |||
950 | while (index < length && text->buffer[index] == ' ') index++; | |||
951 | ||||
952 | return index - start; | |||
953 | } |