vstr.c 9.41 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/*
 * This file is part of the Micro Python project, http://micropython.org/
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2013, 2014 Damien P. George
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

Damien's avatar
Damien committed
27
28
29
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
30
#include <assert.h>
31
32
33

#include "py/mpconfig.h"
#include "py/misc.h"
Damien's avatar
Damien committed
34
35
36
37

// returned value is always at least 1 greater than argument
#define ROUND_ALLOC(a) (((a) & ((~0) - 7)) + 8)

38
void vstr_init(vstr_t *vstr, size_t alloc) {
39
40
41
42
    if (alloc < 2) {
        // need at least 1 byte for the null byte at the end
        alloc = 2;
    }
43
    vstr->alloc = alloc;
Damien's avatar
Damien committed
44
45
46
47
48
49
50
51
    vstr->len = 0;
    vstr->buf = m_new(char, vstr->alloc);
    if (vstr->buf == NULL) {
        vstr->had_error = true;
        return;
    }
    vstr->buf[0] = 0;
    vstr->had_error = false;
52
53
54
    vstr->fixed_buf = false;
}

55
void vstr_init_fixed_buf(vstr_t *vstr, size_t alloc, char *buf) {
56
57
58
59
60
61
62
    assert(alloc > 0); // need at least room for the null byte
    vstr->alloc = alloc;
    vstr->len = 0;
    vstr->buf = buf;
    vstr->buf[0] = 0;
    vstr->had_error = false;
    vstr->fixed_buf = true;
Damien's avatar
Damien committed
63
64
65
}

void vstr_clear(vstr_t *vstr) {
66
67
68
    if (!vstr->fixed_buf) {
        m_del(char, vstr->buf, vstr->alloc);
    }
Damien's avatar
Damien committed
69
70
71
    vstr->buf = NULL;
}

72
vstr_t *vstr_new(void) {
Damien's avatar
Damien committed
73
74
75
76
    vstr_t *vstr = m_new(vstr_t, 1);
    if (vstr == NULL) {
        return NULL;
    }
77
78
79
80
    vstr_init(vstr, 32);
    return vstr;
}

81
vstr_t *vstr_new_size(size_t alloc) {
82
83
84
85
86
    vstr_t *vstr = m_new(vstr_t, 1);
    if (vstr == NULL) {
        return NULL;
    }
    vstr_init(vstr, alloc);
Damien's avatar
Damien committed
87
88
89
90
91
    return vstr;
}

void vstr_free(vstr_t *vstr) {
    if (vstr != NULL) {
92
93
94
        if (!vstr->fixed_buf) {
            m_del(char, vstr->buf, vstr->alloc);
        }
95
        m_del_obj(vstr_t, vstr);
Damien's avatar
Damien committed
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
    }
}

void vstr_reset(vstr_t *vstr) {
    vstr->len = 0;
    vstr->buf[0] = 0;
    vstr->had_error = false;
}

bool vstr_had_error(vstr_t *vstr) {
    return vstr->had_error;
}

char *vstr_str(vstr_t *vstr) {
    if (vstr->had_error) {
        return NULL;
    }
    return vstr->buf;
}

116
size_t vstr_len(vstr_t *vstr) {
Damien's avatar
Damien committed
117
118
119
120
121
122
    if (vstr->had_error) {
        return 0;
    }
    return vstr->len;
}

123
124
// Extend vstr strictly by requested size, return pointer to newly added chunk
char *vstr_extend(vstr_t *vstr, size_t size) {
125
126
127
    if (vstr->fixed_buf) {
        return NULL;
    }
128
129
130
131
132
133
134
135
136
137
138
    char *new_buf = m_renew(char, vstr->buf, vstr->alloc, vstr->alloc + size);
    if (new_buf == NULL) {
        vstr->had_error = true;
        return NULL;
    }
    char *p = new_buf + vstr->alloc;
    vstr->alloc += size;
    vstr->buf = new_buf;
    return p;
}

139
STATIC bool vstr_ensure_extra(vstr_t *vstr, size_t size) {
Damien's avatar
Damien committed
140
    if (vstr->len + size + 1 > vstr->alloc) {
141
142
143
        if (vstr->fixed_buf) {
            return false;
        }
144
        size_t new_alloc = ROUND_ALLOC((vstr->len + size + 1) * 2);
145
        char *new_buf = m_renew(char, vstr->buf, vstr->alloc, new_alloc);
Damien's avatar
Damien committed
146
147
148
149
150
151
152
153
154
155
        if (new_buf == NULL) {
            vstr->had_error = true;
            return false;
        }
        vstr->alloc = new_alloc;
        vstr->buf = new_buf;
    }
    return true;
}

156
void vstr_hint_size(vstr_t *vstr, size_t size) {
Damien's avatar
Damien committed
157
158
159
160
161
162
    // it's not an error if we fail to allocate for the size hint
    bool er = vstr->had_error;
    vstr_ensure_extra(vstr, size);
    vstr->had_error = er;
}

163
char *vstr_add_len(vstr_t *vstr, size_t len) {
Damien's avatar
Damien committed
164
165
166
167
168
169
170
171
172
    if (vstr->had_error || !vstr_ensure_extra(vstr, len)) {
        return NULL;
    }
    char *buf = vstr->buf + vstr->len;
    vstr->len += len;
    vstr->buf[vstr->len] = 0;
    return buf;
}

173
void vstr_add_byte(vstr_t *vstr, byte b) {
Damien's avatar
Damien committed
174
175
176
177
    byte *buf = (byte*)vstr_add_len(vstr, 1);
    if (buf == NULL) {
        return;
    }
178
    buf[0] = b;
Damien's avatar
Damien committed
179
180
181
}

void vstr_add_char(vstr_t *vstr, unichar c) {
182
#if MICROPY_PY_BUILTINS_STR_UNICODE
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
    // TODO: Can this be simplified and deduplicated?
    // Is it worth just calling vstr_add_len(vstr, 4)?
    if (c < 0x80) {
        byte *buf = (byte*)vstr_add_len(vstr, 1);
        if (buf == NULL) {
            return;
        }
        *buf = (byte)c;
    } else if (c < 0x800) {
        byte *buf = (byte*)vstr_add_len(vstr, 2);
        if (buf == NULL) {
            return;
        }
        buf[0] = (c >> 6) | 0xC0;
        buf[1] = (c & 0x3F) | 0x80;
    } else if (c < 0x10000) {
        byte *buf = (byte*)vstr_add_len(vstr, 3);
        if (buf == NULL) {
            return;
        }
        buf[0] = (c >> 12) | 0xE0;
        buf[1] = ((c >> 6) & 0x3F) | 0x80;
        buf[2] = (c & 0x3F) | 0x80;
    } else {
        assert(c < 0x110000);
        byte *buf = (byte*)vstr_add_len(vstr, 4);
        if (buf == NULL) {
            return;
        }
        buf[0] = (c >> 18) | 0xF0;
        buf[1] = ((c >> 12) & 0x3F) | 0x80;
        buf[2] = ((c >> 6) & 0x3F) | 0x80;
        buf[3] = (c & 0x3F) | 0x80;
Damien's avatar
Damien committed
216
    }
217
218
219
220
221
222
223
#else
    byte *buf = (byte*)vstr_add_len(vstr, 1);
    if (buf == NULL) {
        return;
    }
    buf[0] = c;
#endif
Damien's avatar
Damien committed
224
225
226
227
228
229
}

void vstr_add_str(vstr_t *vstr, const char *str) {
    vstr_add_strn(vstr, str, strlen(str));
}

230
void vstr_add_strn(vstr_t *vstr, const char *str, size_t len) {
Damien's avatar
Damien committed
231
    if (vstr->had_error || !vstr_ensure_extra(vstr, len)) {
232
233
234
235
236
237
        // if buf is fixed, we got here because there isn't enough room left
        // so just try to copy as much as we can, with room for null byte
        if (vstr->fixed_buf && vstr->len + 1 < vstr->alloc) {
            len = vstr->alloc - vstr->len - 1;
            goto copy;
        }
Damien's avatar
Damien committed
238
239
        return;
    }
240
copy:
Damien's avatar
Damien committed
241
242
243
244
245
    memmove(vstr->buf + vstr->len, str, len);
    vstr->len += len;
    vstr->buf[vstr->len] = 0;
}

246
STATIC char *vstr_ins_blank_bytes(vstr_t *vstr, size_t byte_pos, size_t byte_len) {
247
248
249
    if (vstr->had_error) {
        return NULL;
    }
250
    size_t l = vstr->len;
251
252
253
254
255
256
257
258
    if (byte_pos > l) {
        byte_pos = l;
    }
    if (byte_len > 0) {
        // ensure room for the new bytes
        if (!vstr_ensure_extra(vstr, byte_len)) {
            return NULL;
        }
259
260
        // copy up the string to make room for the new bytes; +1 for the null byte
        memmove(vstr->buf + byte_pos + byte_len, vstr->buf + byte_pos, l - byte_pos + 1);
261
262
263
264
265
266
        // increase the length
        vstr->len += byte_len;
    }
    return vstr->buf + byte_pos;
}

267
void vstr_ins_byte(vstr_t *vstr, size_t byte_pos, byte b) {
268
269
270
271
272
273
    char *s = vstr_ins_blank_bytes(vstr, byte_pos, 1);
    if (s != NULL) {
        *s = b;
    }
}

274
void vstr_ins_char(vstr_t *vstr, size_t char_pos, unichar chr) {
275
    // TODO UNICODE
276
    char *s = vstr_ins_blank_bytes(vstr, char_pos, 1);
277
278
279
280
281
    if (s != NULL) {
        *s = chr;
    }
}

282
void vstr_cut_head_bytes(vstr_t *vstr, size_t bytes_to_cut) {
283
284
285
    vstr_cut_out_bytes(vstr, 0, bytes_to_cut);
}

286
void vstr_cut_tail_bytes(vstr_t *vstr, size_t len) {
Damien's avatar
Damien committed
287
288
289
290
291
292
293
294
    if (vstr->had_error) {
        return;
    }
    if (len > vstr->len) {
        vstr->len = 0;
    } else {
        vstr->len -= len;
    }
Damien George's avatar
Damien George committed
295
    vstr->buf[vstr->len] = 0;
Damien's avatar
Damien committed
296
297
}

298
void vstr_cut_out_bytes(vstr_t *vstr, size_t byte_pos, size_t bytes_to_cut) {
299
300
301
302
303
304
305
306
307
308
309
310
    if (vstr->had_error || byte_pos >= vstr->len) {
        return;
    } else if (byte_pos + bytes_to_cut >= vstr->len) {
        vstr->len = byte_pos;
        vstr->buf[vstr->len] = 0;
    } else {
        // move includes +1 for null byte at the end
        memmove(vstr->buf + byte_pos, vstr->buf + byte_pos + bytes_to_cut, vstr->len - byte_pos - bytes_to_cut + 1);
        vstr->len -= bytes_to_cut;
    }
}

Damien's avatar
Damien committed
311
void vstr_printf(vstr_t *vstr, const char *fmt, ...) {
312
313
314
315
316
317
318
    va_list ap;
    va_start(ap, fmt);
    vstr_vprintf(vstr, fmt, ap);
    va_end(ap);
}

void vstr_vprintf(vstr_t *vstr, const char *fmt, va_list ap) {
Damien's avatar
Damien committed
319
320
321
322
323
324
    if (vstr->had_error || !vstr_ensure_extra(vstr, strlen(fmt))) {
        return;
    }

    while (1) {
        // try to print in the allocated space
325
        // need to make a copy of the va_list because we may call vsnprintf multiple times
326
        size_t size = vstr->alloc - vstr->len;
327
328
329
330
        va_list ap2;
        va_copy(ap2, ap);
        int n = vsnprintf(vstr->buf + vstr->len, size, fmt, ap2);
        va_end(ap2);
Damien's avatar
Damien committed
331
332

        // if that worked, return
333
        if (n > -1 && (size_t)n < size) {
Damien's avatar
Damien committed
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
            vstr->len += n;
            return;
        }

        // else try again with more space
        if (n > -1) { // glibc 2.1
            // n + 1 is precisely what is needed
            if (!vstr_ensure_extra(vstr, n + 1)) {
                return;
            }
        } else { // glibc 2.0
            // increase to twice the old size
            if (!vstr_ensure_extra(vstr, size * 2)) {
                return;
            }
        }
    }
}