]> git.cworth.org Git - vogl/blob - src/voglcore/vogl_dynamic_string.h
Initial vogl checkin
[vogl] / src / voglcore / vogl_dynamic_string.h
1 /**************************************************************************
2  *
3  * Copyright 2013-2014 RAD Game Tools and Valve Software
4  * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC
5  * All Rights Reserved.
6  *
7  * Permission is hereby granted, free of charge, to any person obtaining a copy
8  * of this software and associated documentation files (the "Software"), to deal
9  * in the Software without restriction, including without limitation the rights
10  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11  * copies of the Software, and to permit persons to whom the Software is
12  * furnished to do so, subject to the following conditions:
13  *
14  * The above copyright notice and this permission notice shall be included in
15  * all copies or substantial portions of the Software.
16  *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23  * THE SOFTWARE.
24  *
25  **************************************************************************/
26
27 // File: vogl_dynamic_string.h
28 //
29 // Dynamic string class with the small string optimization.
30 //
31 // Important note: For historical reasons the comparisons operators in this class are
32 // case insensitive (using locale independent helper functions) by default, mostly because
33 // the last X engines I've worked on used this default and I got used to it. Its the day after
34 // and now I'm kinda regretting this idea, but there's too much code that depends on this behavior.
35 // If you need to do case sensitive comparisons: either call compare(other, false), or dynamic_string_less_than_case_insensitive.
36 #pragma once
37
38 #include "vogl_core.h"
39
40 #ifdef VOGL_BUILD_DEBUG
41 #ifndef VOGL_SLOW_STRING_LEN_CHECKS
42 #define VOGL_SLOW_STRING_LEN_CHECKS 1
43 #endif
44 #endif
45
46 namespace vogl
47 {
48
49     class json_value;
50     class dynamic_string;
51     typedef vogl::vector<dynamic_string> dynamic_string_array;
52
53     // These limits are in place so plain signed int's can be safely used as offsets into the buffer (use something else if you need strings this big).
54     enum
55     {
56         cMaxDynamicStringBufSize = 0x7FFFFFFFU,
57         cMaxDynamicStringLen = cMaxDynamicStringBufSize - 1
58     };
59
60     class dynamic_string
61     {
62     public:
63         inline dynamic_string()
64         {
65             set_to_empty_small_string();
66         }
67         dynamic_string(eVarArg dummy, const char *p, ...) VOGL_ATTRIBUTE_PRINTF(3, 4);
68         dynamic_string(const char *p);
69         dynamic_string(const char *p, uint len);
70         dynamic_string(const dynamic_string &other);
71
72         inline ~dynamic_string()
73         {
74             if (is_dynamic())
75                 vogl_delete_array(m_dyn.m_pStr);
76         }
77
78         // Truncates the string to 0 chars and frees the buffer.
79         void clear();
80         void optimize();
81
82         void reserve(uint new_capacity);
83
84         // Truncates the string to 0 chars, but does not free the buffer.
85         void empty();
86
87         // Returns the string buffer to the caller and clears the string.
88         // The caller takes ownership of the returned buffer, free it using vogl_delete_array(pStr);
89         inline const char *assume_ownership()
90         {
91             ensure_dynamic();
92             const char *p = m_dyn.m_pStr;
93             set_to_empty_small_string();
94             return p;
95         }
96
97         inline uint get_len() const
98         {
99             return m_len;
100         }
101         inline uint size() const
102         {
103             return m_len;
104         }
105         inline bool is_empty() const
106         {
107             return !m_len;
108         }
109         inline bool has_content() const
110         {
111             return m_len != 0;
112         }
113
114         // Returns a pointer to the zero terminated C-style string (which may be a zero terminated empty string) - never NULL.
115         inline const char *get_ptr() const
116         {
117             return get_ptr_priv();
118         }
119         inline const char *c_str() const
120         {
121             return get_ptr_priv();
122         }
123
124         // Returns a pointer to the zero terminated C-style string (which may be a zero terminated empty string) - never NULL.
125         // Do not change the length of the string via this pointer! This method is intended for faster deserialization.
126         inline char *get_ptr_raw()
127         {
128             return get_ptr_priv();
129         }
130
131         // Returns a duplicate of the string.
132         dynamic_string get_clone() const
133         {
134             return dynamic_string(*this);
135         }
136
137         inline char front() const
138         {
139             return m_len ? get_ptr_priv()[0] : '\0';
140         }
141         inline char back() const
142         {
143             return m_len ? get_ptr_priv()[m_len - 1] : '\0';
144         }
145
146         inline char operator[](uint i) const
147         {
148             VOGL_ASSERT(i <= m_len);
149             return get_ptr()[i];
150         }
151
152         // Index string, beginning from back, 0 = last character, 1 = next to last char, etc.
153         inline char get_char_at_end(uint i) const
154         {
155             VOGL_ASSERT(i <= m_len);
156             return (i < m_len) ? get_ptr()[m_len - 1U - i] : '\0';
157         }
158
159         inline string_hash get_hash() const
160         {
161             return string_hash(get_ptr(), m_len);
162         }
163
164         // IMPORTANT: These comparison methods are all CASE INSENSITIVE by default!
165         // Yes this seems maybe sorta nutty to me now, but in the past I've worked on a bunch of projects where this was the default and it was rarely a problem.
166         int compare(const char *p, bool case_sensitive = false) const;
167         int compare(const dynamic_string &rhs, bool case_sensitive = false) const;
168
169         // length of string is the first key, then followed by a lex compare using compare() if lengths are equal
170         int compare_using_length(const char *p, bool case_sensitive = false) const;
171         int compare_using_length(const dynamic_string &rhs, bool case_sensitive = false) const;
172
173         // IMPORTANT: These comparison methods are all CASE INSENSITIVE by default!
174         inline bool operator==(const dynamic_string &rhs) const
175         {
176             return compare(rhs) == 0;
177         }
178         inline bool operator==(const char *p) const
179         {
180             return compare(p) == 0;
181         }
182
183         inline bool operator!=(const dynamic_string &rhs) const
184         {
185             return compare(rhs) != 0;
186         }
187         inline bool operator!=(const char *p) const
188         {
189             return compare(p) != 0;
190         }
191
192         inline bool operator<(const dynamic_string &rhs) const
193         {
194             return compare(rhs) < 0;
195         }
196         inline bool operator<(const char *p) const
197         {
198             return compare(p) < 0;
199         }
200
201         inline bool operator>(const dynamic_string &rhs) const
202         {
203             return compare(rhs) > 0;
204         }
205         inline bool operator>(const char *p) const
206         {
207             return compare(p) > 0;
208         }
209
210         inline bool operator<=(const dynamic_string &rhs) const
211         {
212             return compare(rhs) <= 0;
213         }
214         inline bool operator<=(const char *p) const
215         {
216             return compare(p) <= 0;
217         }
218
219         inline bool operator>=(const dynamic_string &rhs) const
220         {
221             return compare(rhs) >= 0;
222         }
223         inline bool operator>=(const char *p) const
224         {
225             return compare(p) >= 0;
226         }
227
228         friend inline bool operator==(const char *p, const dynamic_string &rhs)
229         {
230             return rhs.compare(p) == 0;
231         }
232
233         dynamic_string &set(const char *p, uint max_len = UINT_MAX);
234         dynamic_string &set(const dynamic_string &other, uint max_len = UINT_MAX);
235
236         bool set_len(uint new_len, char fill_char = ' ');
237
238         // Set from a NON-zero terminated buffer (i.e. pBuf cannot contain any 0's, buf_len_in_chars is the size of the string).
239         dynamic_string &set_from_buf(const void *pBuf, uint buf_len_in_chars);
240
241         dynamic_string &operator=(const dynamic_string &rhs)
242         {
243             return set(rhs);
244         }
245         dynamic_string &operator=(const char *p)
246         {
247             return set(p);
248         }
249
250         dynamic_string &set_char(uint index, char c);
251         dynamic_string &append_char(char c);
252         dynamic_string &append_char(int c)
253         {
254             VOGL_ASSERT((c > 0) && (c <= 255));
255             return append_char(static_cast<char>(c));
256         }
257         dynamic_string &truncate(uint new_len);
258         dynamic_string &shorten(uint chars_to_remove);
259         dynamic_string &tolower();
260         dynamic_string &toupper();
261
262         dynamic_string &append(const char *p);
263         dynamic_string &append(const char *p, uint len);
264         dynamic_string &append(const dynamic_string &other);
265         dynamic_string &operator+=(const char *p)
266         {
267             return append(p);
268         }
269         dynamic_string &operator+=(const dynamic_string &other)
270         {
271             return append(other);
272         }
273
274         friend dynamic_string operator+(const char *p, const dynamic_string &a);
275         friend dynamic_string operator+(const dynamic_string &a, const char *p);
276         friend dynamic_string operator+(const dynamic_string &a, const dynamic_string &b);
277
278         dynamic_string &format_args(const char *p, va_list args);
279         dynamic_string &format(const char *p, ...) VOGL_ATTRIBUTE_PRINTF(2, 3);
280         dynamic_string &format_append(const char *p, ...) VOGL_ATTRIBUTE_PRINTF(2, 3);
281
282         // Note many of these ops are IN-PLACE string operations to avoid constructing new dynamic_string objects.
283         // Yes this is awkward, one way to work around this is to close the string like this (which introduces a clone and then does the op in-place):
284         // result = str.get_clone().left(x);
285         dynamic_string &crop(uint start, uint len);
286         dynamic_string &remove(uint start, uint len);
287
288         // trims string to [start, end), i.e. up to but not including end
289         dynamic_string &substring(uint start, uint end);
290
291         dynamic_string &left(uint len);
292         dynamic_string &mid(uint start, uint len);
293         dynamic_string &right(uint start);
294         dynamic_string &tail(uint num);
295
296         dynamic_string &replace(const char *pFind, const char *pReplacement, bool case_sensitive = true, uint *pNum_found = NULL, uint max_replacements = UINT_MAX);
297
298         dynamic_string &unquote();
299
300         uint count_char(char c) const;
301
302         int find_left(const char *p, bool case_sensitive = false, uint start_ofs = 0) const;
303         int find_left(char c, int start_ofs = 0) const;
304
305         int find_right(char c) const;
306         int find_right(char c, uint start_ofs) const;
307         int find_right(const char *p, bool case_sensitive = false) const;
308
309         bool contains(const char *p, bool case_sensitive = false) const;
310         bool contains(char c) const;
311
312         bool begins_with(const char *p, bool case_sensitive = false) const;
313         bool ends_with(const char *p, bool case_sensitive = false) const;
314
315         dynamic_string &trim();
316         dynamic_string &trim_end();
317         dynamic_string &trim_crlf();
318
319         dynamic_string &remap(int from_char, int to_char);
320
321         void swap(dynamic_string &other);
322
323         void tokenize(const char *pDelims, dynamic_string_array &tokens, bool trim = false) const;
324
325         uint get_serialize_size() const
326         {
327             return sizeof(uint32) + m_len;
328         }
329
330         // Returns -1 on failure, or the number of bytes written.
331         int serialize(void *pBuf, uint buf_size, bool little_endian) const;
332
333         // Returns -1 on failure, or the number of bytes read.
334         int deserialize(const void *pBuf, uint buf_size, bool little_endian);
335
336         void translate_lf_to_crlf();
337
338         static inline char *create_raw_buffer(uint &buf_size_in_chars);
339         static inline void free_raw_buffer(char *p)
340         {
341             vogl_delete_array(p);
342         }
343
344         // Buf size must be a power of 2, or cUINT32_MAX. TODO: Relax this BS.
345         dynamic_string &set_from_raw_buf_and_assume_ownership(char *pBuf, uint buf_size_in_bytes, uint len_in_chars);
346
347         bool validate() const;
348
349         // Forces the string to use a dynamically allocated buffer (the buffer is not optimized for enlarging).
350         void ensure_dynamic();
351
352         // True if the string is dynamic, false if it's static/small
353         inline bool is_dynamic() const
354         {
355             return (m_small.m_flag & 1) == 0;
356         }
357
358         // True if the string is inside of the object, false if it's dynamic.
359         inline bool is_small_string() const
360         {
361             return !is_dynamic();
362         }
363
364     private:
365         enum
366         {
367             cSmallStringExtraBufSize = 4,
368             cSmallStringBufSize = ((sizeof(char *) - 1) + sizeof(uint32)) + cSmallStringExtraBufSize,
369             cSmallStringMaxLen = cSmallStringBufSize - 1,
370         };
371
372         // TODO: Store m_len at the end of the struct, and use 1 byte to hold X-len. When the string is size X, the last byte will be zero, so we can use the entire struct to hold the string.
373         // Current string length in bytes.
374         // In this design, the string can be either static (small) or dynamic, independent of its size.
375         // I could gate whether or not the string is dynamic pased purely off the length, but this would be more restrictive.
376         uint32 m_len;
377
378         union
379         {
380             struct
381             {
382                 // m_pStr is always zero terminated, and never NULL. Obviously, don't access it directly, use get_ptr() or get_ptr_priv() instead.
383                 char *m_pStr;
384
385                 // Current buffer size in bytes.
386                 uint32 m_buf_size;
387             } m_dyn;
388
389             struct
390             {
391                 uint8 m_flag;
392                 char m_buf[cSmallStringBufSize];
393             } m_small;
394         };
395
396         inline void check() const
397         {
398             VOGL_ASSERT(validate());
399         }
400
401         // expands buffer to at least new_buf_size bytes
402         bool expand_buf(uint new_buf_size, bool preserve_contents);
403
404         // ensures a buffer at least large enough to hold len chars + zero terminator
405         bool ensure_buf(uint len, bool preserve_contents = true);
406
407         inline uint get_buf_size() const
408         {
409             return is_dynamic() ? static_cast<uint>(m_dyn.m_buf_size) : static_cast<uint>(cSmallStringBufSize);
410         }
411
412         inline void set_small_string_flag()
413         {
414             m_small.m_flag = 1;
415         }
416
417         inline void set_to_empty_small_string()
418         {
419             m_len = 0;
420             m_small.m_flag = 1;
421             m_small.m_buf[0] = 0;
422         }
423
424         inline void set_dyn_string_ptr(char *p)
425         {
426             VOGL_ASSERT(p);
427             m_dyn.m_pStr = p;
428             VOGL_ASSERT(is_dynamic());
429         }
430
431         // Returns ptr to string, never NULL
432         inline const char *get_ptr_priv() const
433         {
434             return is_dynamic() ? m_dyn.m_pStr : m_small.m_buf;
435         }
436
437         // Returns ptr to string, never NULL
438         inline char *get_ptr_priv()
439         {
440             return is_dynamic() ? m_dyn.m_pStr : m_small.m_buf;
441         }
442
443         inline bool ptr_refers_to_self(const char *p) const
444         {
445             const char *pStr = get_ptr_priv();
446             return (p >= pStr) && (p < (pStr + get_buf_size()));
447         }
448     };
449
450     extern dynamic_string g_empty_dynamic_string;
451
452     VOGL_DEFINE_BITWISE_MOVABLE(dynamic_string);
453
454     inline void swap(dynamic_string &a, dynamic_string &b)
455     {
456         a.swap(b);
457     }
458
459     inline char *dynamic_string::create_raw_buffer(uint &buf_size_in_chars)
460     {
461         buf_size_in_chars = static_cast<uint>(math::minimum<uint64_t>(cUINT32_MAX, math::next_pow2(static_cast<uint64_t>(buf_size_in_chars))));
462         return vogl_new_array(char, buf_size_in_chars);
463     }
464
465     inline int scan_dynamic_string_array_for_string(const dynamic_string_array &strings, const char *pStr, bool case_sensitive)
466     {
467         for (uint i = 0; i < strings.size(); i++)
468             if (!strings[i].compare(pStr, case_sensitive))
469                 return i;
470         return -1;
471     }
472
473     template <>
474     struct hasher<dynamic_string>
475     {
476         inline size_t operator()(const dynamic_string &key) const
477         {
478             return key.get_hash();
479         }
480     };
481
482     struct dynamic_string_less_than_case_insensitive
483     {
484         inline bool operator()(const dynamic_string &a, const dynamic_string &b) const
485         {
486             return a.compare(b, false) < 0;
487         }
488     };
489
490     struct dynamic_string_equal_to_case_insensitive
491     {
492         inline bool operator()(const dynamic_string &a, const dynamic_string &b) const
493         {
494             return a.compare(b, false) == 0;
495         }
496     };
497
498     struct dynamic_string_less_than_case_sensitive
499     {
500         inline bool operator()(const dynamic_string &a, const dynamic_string &b) const
501         {
502             return a.compare(b, true) < 0;
503         }
504     };
505
506     struct dynamic_string_equal_to_case_sensitive
507     {
508         inline bool operator()(const dynamic_string &a, const dynamic_string &b) const
509         {
510             return a.compare(b, true) == 0;
511         }
512     };
513
514     bool json_serialize(const dynamic_string &str, json_value &val);
515     bool json_deserialize(dynamic_string &str, const json_value &val);
516
517     bool dynamic_string_test();
518
519 } // namespace vogl
520
521 namespace std
522 {
523     template <>
524     inline void swap(vogl::dynamic_string &a, vogl::dynamic_string &b)
525     {
526         a.swap(b);
527     }
528
529 } // namespace std
530