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