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->str_params.char_size = char_size;
 34
 35    // check if string can be integral into result
 36
 37    if (capacity <= integral_capacity[char_size]) {
 38        result->str_params.allocated = 0;
 39        result->str_params.integral = 1;
 40        result->integral_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(PweStringTooLong));
 49        return false;
 50    }
 51
 52    result->str_params.integral = 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->str_params.integral = 0;
 68    result->str_params.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->str_params.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->str_params.allocated) {
 84        _PwStringData* orig_sdata = str->string_data;
 85        char_ptr = orig_sdata->data;
 86    } else {
 87        // static string here, integral 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->str_params.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    if (str->str_params.integral) {
106        unsigned new_length = str->integral_length + increment;
107        if (_pw_likely(new_length <= integral_capacity[new_char_size])) {
108            // no need to expand
109            if (_pw_unlikely(new_char_size > char_size)) {
110                // but need to make existing chars wider
111                _PwValue orig_str = *str;
112                str->str_params.char_size = new_char_size;
113
114                StrCopy fn_copy = _pw_strcopy_variants[new_char_size][char_size];
115                uint8_t* src_end_ptr;
116                uint8_t* src_start_ptr = _pw_string_start_end(&orig_str, &src_end_ptr);
117                fn_copy(_pw_string_start(str), src_start_ptr, src_end_ptr);
118            }
119            return true;
120        }
121        // increased capacity is beyond integral capacity; go copy
122
123    } else if (str->str_params.allocated) {
124
125        if (new_char_size == char_size) {
126            unsigned new_capacity = str->length + increment;
127
128            if (_pw_likely(_pw_atomic_load(&str->string_data->refcount) == 1)) {
129
130                if (_pw_likely(new_capacity <= str->string_data->capacity)) {
131                    // no need to expand
132                    return true;
133                }
134                // expand string in-place
135
136                if (_pw_unlikely(increment > _max_capacity[char_size] - str->length)) {
137                    pw_set_status(PwStatus(PweStringTooLong));
138                    return false;
139                }
140                unsigned orig_memsize = _pw_allocated_string_data_size(str);
141                unsigned new_memsize = _pw_calc_string_data_size(char_size, new_capacity, &str->string_data->capacity);
142
143                // reallocate data
144                return _pw_realloc(str->type_id, (void**) &str->string_data, orig_memsize, new_memsize, false);
145            }
146        }
147    }
148
149    // make a copy of string
150
151    unsigned length = pw_strlen(str);
152    if (_pw_unlikely(increment > _max_capacity[new_char_size] - length)) {
153        pw_set_status(PwStatus(PweStringTooLong));
154        return false;
155    }
156
157    // allocate string
158    PwValue new_str = PW_NULL;
159    unsigned new_capacity = length + increment;
160    if (!_pw_make_empty_string(str->type_id, new_capacity, new_char_size, &new_str)) {
161        return false;
162    }
163    // copy original string to the new string
164    StrCopy fn_copy = _pw_strcopy_variants[new_char_size][char_size];
165    uint8_t* src_end_ptr;
166    uint8_t* src_start_ptr = _pw_string_start_end(str, &src_end_ptr);
167    fn_copy(_pw_string_start(&new_str), src_start_ptr, src_end_ptr);
168    _pw_string_set_length(&new_str, length);
169
170    pw_move(str, &new_str);
171
172    return true;
173}
174
175/****************************************************************
176 * Constructors
177 */
178
179[[nodiscard]] bool pw_create_empty_string(unsigned capacity, uint8_t char_size, PwValuePtr result)
180{
181    return _pw_make_empty_string(PwTypeId_String, capacity, char_size, result);
182}