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}