diff --git a/src/min_array.h b/src/min_array.h index dd29a3b..266a4bb 100644 --- a/src/min_array.h +++ b/src/min_array.h @@ -10,6 +10,7 @@ #include /* assert */ #define MIN_ARRAY_IDLE { 0, 0, 0 } +#define MIN_ARRAY_CAPACITY 3 #define MIN_ARRAY(name, type) \ struct name##_array { type *data; size_t size, capacity; }; \ /** Initialises `a` to idle. */ \ @@ -23,23 +24,25 @@ static void name##_array_(struct name##_array *const a) \ @throws[ERANGE] Tried allocating more then can fit in `size_t` or `realloc` doesn't follow POSIX. @throws[realloc, ERANGE] */ \ static int name##_array_reserve(struct name##_array *const a, \ - const size_t min_capacity) { \ + const size_t min) { \ size_t c0; \ type *data; \ const size_t max_size = (size_t)-1 / sizeof *a->data; \ assert(a); \ if(a->data) { \ assert(a->size <= a->capacity); \ - if(min_capacity <= a->capacity) return 1; \ - c0 = a->capacity; \ + if(min <= a->capacity) return 1; \ + c0 = a->capacity < MIN_ARRAY_CAPACITY \ + ? MIN_ARRAY_CAPACITY : a->capacity; \ } else { /* Idle. */ \ - if(!min_capacity) return 1; \ - c0 = 1; \ + assert(!a->size && !a->capacity); \ + if(!min) return 1; \ + c0 = MIN_ARRAY_CAPACITY; \ } \ - if(min_capacity > max_size) return errno = ERANGE, 0; \ + if(min > max_size) return errno = ERANGE, 0; \ /* `c_n = a1.625^n`, approximation golden ratio `\phi ~ 1.618`. */ \ - while(c0 < min_capacity) { \ - size_t c1 = c0 + (c0 >> 1) + (c0 >> 3) + 1; \ + while(c0 < min) { \ + size_t c1 = c0 + (c0 >> 1) + (c0 >> 3); \ if(c0 > c1) { c0 = max_size; break; } \ c0 = c1; \ } \ @@ -48,34 +51,37 @@ static int name##_array_reserve(struct name##_array *const a, \ a->data = data, a->capacity = c0; \ return 1; \ } \ -/** Makes sure that there are at least `buffer` contiguous, un-initialised, - elements at the back of `a`. - @return A pointer to the start of `buffer` elements, namely `a.data + a.size`. - If `a` is idle and `buffer` is zero, a null pointer is returned, otherwise - null indicates an error and `errno` will be set. @throws[realloc, ERANGE] */ \ +/** The capacity of `a` will be increased to at least `n` elements beyond the + size. Invalidates any pointers in `a`. + @return The start of the buffered space at the back of the array. If `a` is + idle and `buffer` is zero, a null pointer is returned, otherwise null + indicates an error. @throws[realloc, ERANGE] */ \ static type *name##_array_buffer(struct name##_array *const a, \ - const size_t buffer) { \ - assert(a); \ - if(a->size > (size_t)-1 - buffer) \ - { errno = ERANGE; return 0; } /* Unlikely. */ \ - if(!name##_array_reserve(a, a->size + buffer)) return 0; \ - return a->data ? a->data + a->size : 0; \ -} \ -/** Adds `n` to the size of `a`; this must be no more than the maximum - remaining buffer capacity, set by _array_buffer>. */ \ -static void name##_array_emplace(struct name##_array *const a, \ const size_t n) { \ - assert(a && a->capacity >= a->size && n <= a->capacity - a->size); \ - a->size += n; \ + assert(a); \ + if(a->size > (size_t)-1 - n) { errno = ERANGE; return 0; } /* Unlikely. */ \ + return name##_array_reserve(a, a->size + n) && a->data \ + ? a->data + a->size : 0; \ } \ -/** @return Push back a new un-initialized datum of `a`. +/** Adds `n` elements to the back of `a`. It will invalidate pointers in `a` if + `n` is greater than the buffer space. + @return A pointer to the elements. If `a` is idle and `n` is zero, a null + pointer will be returned, otherwise null indicates an error. @throws[realloc, ERANGE] */ \ -static type *name##_array_new(struct name##_array *const a) { \ - type *const data = name##_array_buffer(a, 1); \ - return data ? name##_array_emplace(a, 1), data : 0; \ +static type *name##_array_append(struct name##_array *const a, \ + const size_t n) { \ + type *buffer; \ + assert(a); \ + if(!(buffer = name##_array_buffer(a, n))) return 0; \ + assert(n <= a->capacity && a->size <= a->capacity - n); \ + return a->size += n, buffer; \ } \ +/** @return Adds (push back) one new element of `a`. The buffer holds an + element or it will invalidate pointers in `a`. @throws[realloc, ERANGE] */ \ +static type *name##_array_new(struct name##_array *const a) \ + { return name##_array_append(a, 1); } \ /* It's perfectly valid that these functions are not used. */ \ static void name##_unused_coda(void); static void name##_unused(void) { \ - name##_array(0); name##_array_buffer(0, 0); name##_array_emplace(0, 0); \ + name##_array(0); name##_array_buffer(0, 0); name##_array_append(0, 0); \ name##_array_new(0); name##_unused_coda(); } \ static void name##_unused_coda(void) { name##_unused(); } diff --git a/test/test_array.c b/test/test_array.c index 95f779e..2026caa 100644 --- a/test/test_array.c +++ b/test/test_array.c @@ -16,8 +16,8 @@ struct num { size_t line; int num; }; MIN_ARRAY(char, char) MIN_ARRAY(num, struct num) -/** Reads the contents of `fp` after the file pointer and copies them to - `string`; adds a null-terminator. +/** Reads the contents of `fp` after the file pointer and appends them to + `string`; adds a null-terminator. Does not check for nulls in `fp` or `string`. @return Success, otherwise `string` may have read a partial read and may not be terminated. @throws[fread, malloc] */ static int fp_to_str(FILE *const fp, struct char_array *const string) { @@ -25,10 +25,10 @@ static int fp_to_str(FILE *const fp, struct char_array *const string) { size_t nread; char *cursor; assert(fp && string); - do { - if(!(cursor = char_array_buffer(string, granularity))) return 0; - char_array_emplace(string, nread = fread(cursor, 1, granularity, fp)); - } while(nread == granularity); assert(nread < granularity); + do if(!(cursor = char_array_buffer(string, granularity)) + || !char_array_append(string, + nread = fread(cursor, 1, granularity, fp))) return 0; + while(nread == granularity); assert(nread < granularity); if(ferror(fp) || !(cursor = char_array_new(string))) return 0; *cursor = '\0'; return 1;