1#include "include/pw.h"
  2#include "src/pw_alloc.h"
  3#include "src/types/string/string_internal.h"
  4
  5// lookup table to validate capacity
  6
  7#define _header_size  offsetof(_PwStringData, data)
  8
  9static unsigned _max_capacity[5] = {
 10    0,
 11    0xFFFF'FFFF - _header_size,
 12    (0xFFFF'FFFF - _header_size) / 2,
 13    (0xFFFF'FFFF - _header_size) / 3,
 14    (0xFFFF'FFFF - _header_size) / 4
 15};
 16
 17unsigned _pw_calc_string_data_size(uint8_t char_size, unsigned desired_capacity, unsigned* real_capacity)
 18{
 19    unsigned size = offsetof(_PwStringData, data) + char_size * desired_capacity + PWSTRING_BLOCK_SIZE - 1;
 20    size &= ~(PWSTRING_BLOCK_SIZE - 1);
 21    if (real_capacity) {
 22        *real_capacity = (size - offsetof(_PwStringData, data)) / char_size;
 23    }
 24    return size;
 25}
 26
 27[[nodiscard]] bool _pw_make_empty_string(uint16_t type_id, unsigned capacity, uint8_t char_size, PwValuePtr result)
 28{
 29    pw_assert(1 <= char_size && char_size <= 4);
 30
 31    pw_destroy(result);
 32    result->type_id = type_id;
 33    result->char_size = char_size;
 34
 35    // check if string can be embedded into result
 36
 37    if (capacity <= embedded_capacity[char_size]) {
 38        result->allocated = 0;
 39        result->embedded = 1;
 40        result->embedded_length = 0;
 41        result->str_4[0] = 0;
 42        result->str_4[1] = 0;
 43        result->str_4[2] = 0;
 44        return true;
 45    }
 46
 47    if(capacity > _max_capacity[char_size]) {
 48        pw_set_status(PwStatus(PW_ERROR_STRING_TOO_LONG));
 49        return false;
 50    }
 51
 52    result->embedded = 0;
 53    result->length = 0;
 54
 55    // allocate string
 56
 57    unsigned real_capacity;
 58    unsigned memsize = _pw_calc_string_data_size(char_size, capacity, &real_capacity);
 59
 60    _PwStringData* string_data = _pw_alloc(result->type_id, memsize, false);
 61    if (!string_data) {
 62        return false;
 63    }
 64    _pw_atomic_store(&string_data->refcount, 1);
 65    string_data->capacity = real_capacity;
 66    result->string_data = string_data;
 67    result->embedded = 0;
 68    result->allocated = 1;
 69    return true;
 70}
 71
 72[[nodiscard]] bool _pw_string_do_copy_on_write(PwValuePtr str)
 73{
 74    unsigned length = str->length;
 75    uint8_t char_size = str->char_size;
 76
 77    // allocate string
 78    PwValue s = PW_NULL;
 79    if (!_pw_make_empty_string(str->type_id, length, char_size, &s)) {
 80        return false;
 81    }
 82    uint8_t* char_ptr;
 83    if (str->allocated) {
 84        _PwStringData* orig_sdata = str->string_data;
 85        char_ptr = orig_sdata->data;
 86    } else {
 87        // static string here, embedded case is filtered out by _pw_string_need_copy_on_write
 88        char_ptr = str->char_ptr;
 89    }
 90    // copy original string to new string
 91    memcpy(_pw_string_start(&s), char_ptr, length * char_size);
 92    _pw_string_set_length(&s, length);
 93    pw_move(str, &s);
 94    return true;
 95}
 96
 97[[nodiscard]] bool _pw_expand_string(PwValuePtr str, unsigned increment, uint8_t new_char_size)
 98{
 99    uint8_t char_size = str->char_size;
100    if (_pw_unlikely(new_char_size < char_size)) {
101        // current char_size is greater than new one, use current as new:
102        new_char_size = char_size;
103    }
104
105    unsigned old_capacity = 0;
106
107    if (str->embedded) {
108        unsigned new_length = str->embedded_length + increment;
109        if (_pw_likely(new_length <= embedded_capacity[new_char_size])) {
110            // no need to expand
111            if (_pw_unlikely(new_char_size > char_size)) {
112                // but need to make existing chars wider
113                _PwValue orig_str = *str;
114                str->char_size = new_char_size;
115
116                StrCopy fn_copy = _pw_strcopy_variants[new_char_size][char_size];
117                uint8_t* src_end_ptr;
118                uint8_t* src_start_ptr = _pw_string_start_end(&orig_str, &src_end_ptr);
119                fn_copy(_pw_string_start(str), src_start_ptr, src_end_ptr);
120            }
121            return true;
122        }
123        // increased capacity is beyond embedded capacity; go copy
124        // !! not necessary: old_capacity = embedded_capacity[char_size];
125
126    } else if (str->allocated) {
127
128        old_capacity = str->string_data->capacity;
129
130        if (new_char_size == char_size) {
131            unsigned new_capacity = str->length + increment;
132
133            if (_pw_likely(new_capacity <= str->string_data->capacity)) {
134                // no need to expand
135                return true;
136            }
137            if (_pw_likely(_pw_atomic_load(&str->string_data->refcount) == 1)) {
138
139                // expand string in-place
140
141                if (_pw_unlikely(increment > _max_capacity[char_size] - str->length)) {
142                    pw_set_status(PwStatus(PW_ERROR_STRING_TOO_LONG));
143                    return false;
144                }
145                unsigned orig_memsize = _pw_allocated_string_data_size(str);
146                unsigned new_memsize = _pw_calc_string_data_size(char_size, new_capacity, &str->string_data->capacity);
147
148                // reallocate data
149                return _pw_realloc(str->type_id, (void**) &str->string_data, orig_memsize, new_memsize, false);
150            }
151        }
152    }
153
154    // make a copy of string
155
156    unsigned length = pw_strlen(str);
157    unsigned new_capacity = length + increment;
158
159    // preserve memory size
160    if (new_capacity * new_char_size < old_capacity * char_size) {
161        new_capacity = old_capacity * char_size / new_char_size;
162        pw_assert(new_capacity >= length + increment);
163    }
164
165    if (_pw_unlikely(increment > _max_capacity[new_char_size] - length)) {
166        pw_set_status(PwStatus(PW_ERROR_STRING_TOO_LONG));
167        return false;
168    }
169
170    // allocate string
171    PwValue new_str = PW_NULL;
172    if (!_pw_make_empty_string(str->type_id, new_capacity, new_char_size, &new_str)) {
173        return false;
174    }
175    // copy original string to new string
176    StrCopy fn_copy = _pw_strcopy_variants[new_char_size][char_size];
177    uint8_t* src_end_ptr;
178    uint8_t* src_start_ptr = _pw_string_start_end(str, &src_end_ptr);
179    fn_copy(_pw_string_start(&new_str), src_start_ptr, src_end_ptr);
180    _pw_string_set_length(&new_str, length);
181
182    pw_move(str, &new_str);
183
184    return true;
185}
186
187/****************************************************************
188 * Constructors
189 */
190
191[[nodiscard]] bool pw_create_empty_string(unsigned capacity, uint8_t char_size, PwValuePtr result)
192{
193    return _pw_make_empty_string(PwTypeId_String, capacity, char_size, result);
194}
195
196[[nodiscard]] bool _pw_create_string(PwValuePtr result, PwValuePtr initializer)
197{
198    if (!pw_create_empty_string(0, 1, result)) {
199        return false;
200    }
201    return pw_string_append(result, initializer);
202}
203
204[[nodiscard]] bool _pw_create_string_ascii(PwValuePtr result, char* initializer)
205{
206    if (!pw_create_empty_string(0, 1, result)) {
207        return false;
208    }
209    return pw_string_append(result, initializer, nullptr);
210}
211
212[[nodiscard]] bool _pw_create_string_utf8(PwValuePtr result, char8_t* initializer)
213{
214    if (!pw_create_empty_string(0, 1, result)) {
215        return false;
216    }
217    return pw_string_append(result, initializer, nullptr);
218}
219
220[[nodiscard]] bool _pw_create_string_utf32(PwValuePtr result, char32_t* initializer)
221{
222    if (!pw_create_empty_string(0, 1, result)) {
223        return false;
224    }
225    return pw_string_append(result, initializer, nullptr);
226}