This repository has been archived on 2021-11-20. You can view files and clone it, but cannot push or open issues or pull requests.
dynarray/dynarray.c

527 lines
14 KiB
C

/*
* This file is part of corelibs. (https://git.redxen.eu/corelibs)
* Copyright (c) 2021 Alex-David Denes
*
* corelibs is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* corelibs is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with corelibs. If not, see <https://www.gnu.org/licenses/>.
*/
#include "dynarray.h"
#include <stdbool.h> // bool
#include <stdlib.h> // malloc() free()
#include <string.h> // memcpy()
// Exporter
static cl_dynarray_err corelibs_dynarray_export_slice(const cl_dynarray_t*, uintmax_t, uintmax_t, void*);
// Lifetime
static cl_dynarray_err corelibs_dynarray_make_new(size_t, cl_dynarray_t**);
static cl_dynarray_err corelibs_dynarray_make_slice(const cl_dynarray_t*, uintmax_t, uintmax_t, cl_dynarray_t**);
static cl_dynarray_err corelibs_dynarray_free(cl_dynarray_t*);
// Array storage
static cl_dynarray_err corelibs_dynarray_mod_arr_cap(cl_dynarray_t*, uintmax_t);
static cl_dynarray_err corelibs_dynarray_mod_arr_lock(cl_dynarray_t*, bool);
// Array contents
static cl_dynarray_err corelibs_dynarray_mod_ct_app(cl_dynarray_t*, uintmax_t, const void*);
static cl_dynarray_err corelibs_dynarray_mod_ct_ins(cl_dynarray_t*, uintmax_t, uintmax_t, const void*);
static cl_dynarray_err corelibs_dynarray_mod_ct_rep(cl_dynarray_t*, uintmax_t, uintmax_t, const void*);
static cl_dynarray_err corelibs_dynarray_mod_ct_rm(cl_dynarray_t*, uintmax_t, uintmax_t);
// Get array properties
static cl_dynarray_err corelibs_dynarray_get_len(const cl_dynarray_t*, uintmax_t*);
static cl_dynarray_err corelibs_dynarray_get_size(const cl_dynarray_t*, size_t*);
static cl_dynarray_err corelibs_dynarray_get_cap_len(const cl_dynarray_t*, uintmax_t*);
static cl_dynarray_err corelibs_dynarray_get_cap_lock(const cl_dynarray_t*, bool*);
// Compare arrays
static cl_dynarray_err corelibs_dynarray_cmp_data(const cl_dynarray_t* a, const cl_dynarray_t* b, bool* eq);
// Private functions
static cl_dynarray_err corelibs_dynarray_bcheck(const cl_dynarray_t*, uintmax_t, uintmax_t);
struct cl_dynarray_t {
void* addr; // Location of element
uintmax_t len; // Element count
struct {
bool lock; // Is the capacity frozen?
uintmax_t len; // Capacity
} cap;
size_t es; // Size of element
};
enum { // Avoid collision in error numbers
CORELIBS_DYNARRAY_ERR_VAR_NCOMPAT = -7,
CORELIBS_DYNARRAY_ERR_VAR_INVAL,
CORELIBS_DYNARRAY_ERR_VAR_IMMUT,
CORELIBS_DYNARRAY_ERR_VAR_UNDEF,
CORELIBS_DYNARRAY_ERR_MEM_OOB,
CORELIBS_DYNARRAY_ERR_MEM_NULL,
CORELIBS_DYNARRAY_ERR_MEM_ALLOC,
CORELIBS_DYNARRAY_ERR_UNKOWN = 0,
CORELIBS_DYNARRAY_ERR_OK = 1,
};
// Present a unified structure that may receive changes, deprecations and renames without hassle for external users
const struct corelibs_dynarray_interface cl_dynarray = {
.make = {
.new = corelibs_dynarray_make_new,
.slice = corelibs_dynarray_make_slice,
},
.free = corelibs_dynarray_free,
.mod = {
.arr = {
.cap = corelibs_dynarray_mod_arr_cap,
.lock = corelibs_dynarray_mod_arr_lock,
},
.ct = {
.app = corelibs_dynarray_mod_ct_app,
.ins = corelibs_dynarray_mod_ct_ins,
.rep = corelibs_dynarray_mod_ct_rep,
.rm = corelibs_dynarray_mod_ct_rm,
},
},
.cmp = {
.data = corelibs_dynarray_cmp_data,
},
.export = {
.slice = corelibs_dynarray_export_slice,
},
.get = {
.len = corelibs_dynarray_get_len,
.size = corelibs_dynarray_get_size,
.cap = {
.len = corelibs_dynarray_get_cap_len,
.lock = corelibs_dynarray_get_cap_lock,
},
},
.err = {
.ok = CORELIBS_DYNARRAY_ERR_OK,
.unknown = CORELIBS_DYNARRAY_ERR_UNKOWN,
.mem = {
.alloc = CORELIBS_DYNARRAY_ERR_MEM_ALLOC,
.null = CORELIBS_DYNARRAY_ERR_MEM_NULL,
.oob = CORELIBS_DYNARRAY_ERR_MEM_OOB,
},
.var = {
.undef = CORELIBS_DYNARRAY_ERR_VAR_UNDEF,
.immut = CORELIBS_DYNARRAY_ERR_VAR_IMMUT,
.inval = CORELIBS_DYNARRAY_ERR_VAR_INVAL,
.ncompat = CORELIBS_DYNARRAY_ERR_VAR_NCOMPAT,
},
},
};
static cl_dynarray_err
corelibs_dynarray_make_new(size_t sbyte, cl_dynarray_t** ptr)
{
cl_dynarray_err err = CORELIBS_DYNARRAY_ERR_OK;
if (ptr == NULL) {
err = CORELIBS_DYNARRAY_ERR_MEM_NULL;
goto ret;
}
cl_dynarray_t* new = malloc(sizeof(*new));
if (new == NULL) {
err = CORELIBS_DYNARRAY_ERR_MEM_ALLOC;
goto ret;
}
new->addr = NULL;
new->es = sbyte;
new->len = 0;
new->cap.len = 0;
new->cap.lock = false;
*ptr = new;
ret:
return err;
}
static cl_dynarray_err
corelibs_dynarray_make_slice(const cl_dynarray_t* arr, uintmax_t pos, uintmax_t cnt, cl_dynarray_t** save)
{
cl_dynarray_err err = CORELIBS_DYNARRAY_ERR_OK;
if (save == NULL || arr == NULL) {
err = CORELIBS_DYNARRAY_ERR_MEM_NULL;
goto ret;
}
if (cnt == 0) {
// Don't copy anything, just make new array
err = corelibs_dynarray_make_new(arr->es, save);
goto ret; // Return even if no error is met, nothing more to do
}
// Can we slice these elements?
if ((err = corelibs_dynarray_bcheck(arr, pos, cnt)) != CORELIBS_DYNARRAY_ERR_OK) goto ret;
cl_dynarray_t* new;
// Create new array
if ((err = corelibs_dynarray_make_new(arr->es, &new)) != CORELIBS_DYNARRAY_ERR_OK) goto ret;
// Resize array to fit contents
if ((err = corelibs_dynarray_mod_arr_cap(new, cnt)) != CORELIBS_DYNARRAY_ERR_OK) {
corelibs_dynarray_free(new);
goto ret;
}
// Copy contents from old array to new one
if ((err = corelibs_dynarray_export_slice(arr, pos, cnt, new->addr)) != CORELIBS_DYNARRAY_ERR_OK) {
corelibs_dynarray_free(new);
goto ret;
}
new->len = cnt;
new->cap.lock = arr->cap.lock; // Inherit capacity lock
*save = new;
ret:
return err;
}
static cl_dynarray_err
corelibs_dynarray_free(cl_dynarray_t* arr)
{
cl_dynarray_err err = CORELIBS_DYNARRAY_ERR_OK;
if (arr == NULL) {
err = CORELIBS_DYNARRAY_ERR_MEM_NULL;
goto ret;
}
if ((err = corelibs_dynarray_mod_arr_cap(arr, 0)) != CORELIBS_DYNARRAY_ERR_OK) goto ret;
free(arr);
ret:
return err;
}
static cl_dynarray_err
corelibs_dynarray_mod_arr_cap(cl_dynarray_t* arr, uintmax_t len)
{
cl_dynarray_err err = CORELIBS_DYNARRAY_ERR_OK;
if (arr == NULL) {
err = CORELIBS_DYNARRAY_ERR_MEM_NULL;
goto ret;
}
if (len == 0) {
free(arr->addr);
arr->cap.len = 0;
goto ret; // Return success (default)
}
if (arr->cap.lock) {
err = CORELIBS_DYNARRAY_ERR_VAR_IMMUT;
goto ret;
}
void* reg = NULL;
const size_t nl = len * arr->es;
if (arr->addr == NULL) {
if ((reg = malloc(nl)) == NULL) {
err = CORELIBS_DYNARRAY_ERR_MEM_ALLOC;
goto ret;
}
} else if (arr->cap.len != len) {
if ((reg = realloc(arr->addr, nl)) == NULL) {
err = CORELIBS_DYNARRAY_ERR_MEM_ALLOC;
goto ret;
}
}
arr->cap.len = len;
arr->addr = reg;
if (arr->cap.len < arr->len) arr->len = arr->cap.len; // Cut out discarded elements if resized to shorter size
ret:
return err;
}
static cl_dynarray_err
corelibs_dynarray_mod_arr_lock(cl_dynarray_t* arr, bool lock)
{
cl_dynarray_err err = CORELIBS_DYNARRAY_ERR_OK;
if (arr == NULL) {
err = CORELIBS_DYNARRAY_ERR_MEM_NULL;
goto ret;
}
arr->cap.lock = lock;
ret:
return err;
}
static cl_dynarray_err
corelibs_dynarray_mod_ct_app(cl_dynarray_t* arr, uintmax_t cnt, const void* elem)
{
return corelibs_dynarray_mod_ct_ins(arr, arr->len, cnt, elem);
}
static cl_dynarray_err
corelibs_dynarray_mod_ct_ins(cl_dynarray_t* arr, uintmax_t pos, uintmax_t cnt, const void* elem)
{
cl_dynarray_err err = CORELIBS_DYNARRAY_ERR_OK;
if (arr == NULL || elem == NULL) {
err = CORELIBS_DYNARRAY_ERR_MEM_NULL;
goto ret;
}
// Resize array to fit inserted objects
if ((err = corelibs_dynarray_mod_arr_cap(arr, ((arr->cap.len < pos) ? pos : arr->cap.len) + cnt)) != CORELIBS_DYNARRAY_ERR_OK) goto ret;
// CHECKPOINT: From here it is safe to commit changes as required conditions are met
// and no errors should be possible (unless you have some special hardware)
uintmax_t clen = arr->len; // Lenght pre-resize
if (arr->len < pos) arr->len = pos;
arr->len += cnt;
uintptr_t src, // Source of copy address without pointer aritmethic
dest; // Destination to copy to
size_t bcnt; // Amount of bytes to copy
// If we are inserting and not appending
if (pos < clen) {
src = (uintptr_t) arr->addr + (pos * arr->es);
dest = src + (cnt * arr->es);
bcnt = (clen - pos) * arr->es;
memmove((void*) dest, (void*) src, bcnt); // Shift bytes to after insertion region
}
src = (uintptr_t) elem;
dest = (uintptr_t) arr->addr + (pos * arr->es);
bcnt = cnt * arr->es;
memcpy((void*) dest, (void*) src, bcnt); // No overlaps possible
ret:
return err;
}
static cl_dynarray_err
corelibs_dynarray_mod_ct_rep(cl_dynarray_t* arr, uintmax_t pos, uintmax_t cnt, const void* elem)
{
cl_dynarray_err err = CORELIBS_DYNARRAY_ERR_OK;
if (arr == NULL || elem == NULL) {
err = CORELIBS_DYNARRAY_ERR_MEM_NULL;
goto ret;
}
if (corelibs_dynarray_bcheck(arr, pos, cnt) == CORELIBS_DYNARRAY_ERR_MEM_OOB) {
// Resize array to fit new elements
if ((err = corelibs_dynarray_mod_arr_cap(arr, pos + cnt)) != CORELIBS_DYNARRAY_ERR_OK) goto ret;
}
// CHECKPOINT: From here it is safe to commit changes as required conditions are met
// and no errors should be possible (unless you have some special hardware)
uintptr_t src, // Source of copy address without pointer aritmethic
dest; // Destination to copy to
size_t bcnt; // Amount of bytes to copy
src = (uintptr_t) elem;
dest = (uintptr_t) arr->addr + (pos * arr->es);
bcnt = cnt * arr->es;
memcpy((void*) dest, (void*) src, bcnt);
if (arr->len < pos + cnt) arr->len = pos + cnt; // Set length at end of replacement if not already same or longer
ret:
return err;
}
static cl_dynarray_err
corelibs_dynarray_mod_ct_rm(cl_dynarray_t* arr, uintmax_t pos, uintmax_t cnt)
{
cl_dynarray_err err = CORELIBS_DYNARRAY_ERR_OK;
if (arr == NULL) {
err = CORELIBS_DYNARRAY_ERR_MEM_NULL;
goto ret;
}
// Are we removing elements we don't have?
if ((err = corelibs_dynarray_bcheck(arr, pos, cnt)) != CORELIBS_DYNARRAY_ERR_OK) goto ret;
// Allocate intermediate buffer
void* tbuf = malloc(arr->es * arr->len);
if (tbuf == NULL) {
err = CORELIBS_DYNARRAY_ERR_MEM_ALLOC;
goto ret;
}
// CHECKPOINT: From here it is safe to commit changes as required conditions are met
// and no errors should be possible (unless you have some special hardware)
arr->len -= cnt; // We remove cnt elements from array
uintptr_t src, // Source of copy address without pointer aritmethic
dest; // Destination to copy to
size_t bcnt; // Amount of bytes to copy
dest = (uintptr_t) tbuf;
src = (uintptr_t) arr->addr;
bcnt = arr->len * arr->es;
memcpy((void*) dest, (void*) src, bcnt); // Copy current contents up to arr->len to intermediate buffer
// If tail is the only thing removed, don't copy back
if (pos + cnt != arr->len) {
// Copy back slice and overwrite region removed
dest += pos * arr->es;
src += (pos + cnt) * arr->es;
bcnt -= pos * arr->es;
memcpy((void*) dest, (void*) src, bcnt);
}
free(arr->addr);
arr->addr = tbuf; // Swap buffers and free previous one
ret:
return err;
}
static cl_dynarray_err
corelibs_dynarray_export_slice(const cl_dynarray_t* arr, uintmax_t pos, uintmax_t cnt, void* save)
{
cl_dynarray_err err = CORELIBS_DYNARRAY_ERR_OK;
if (arr == NULL || save == NULL) {
err = CORELIBS_DYNARRAY_ERR_MEM_NULL;
goto ret;
}
if (cnt == 0) goto ret;
// Do we have the requested slice?
if ((err = corelibs_dynarray_bcheck(arr, pos, cnt)) != CORELIBS_DYNARRAY_ERR_OK) goto ret;
uintptr_t src, // Source of copy address without pointer aritmethic
dest; // Destination to copy to
size_t bcnt; // Amount of bytes to copy
src = (uintptr_t) arr->addr + (arr->es * pos);
dest = (uintptr_t) save;
bcnt = arr->es * cnt;
memcpy((void*) dest, (void*) src, bcnt);
ret:
return err;
}
static cl_dynarray_err
corelibs_dynarray_get_len(const cl_dynarray_t* arr, uintmax_t* save)
{
cl_dynarray_err err = CORELIBS_DYNARRAY_ERR_OK;
if (arr == NULL || save == NULL) {
err = CORELIBS_DYNARRAY_ERR_MEM_NULL;
goto ret;
}
*save = arr->len;
ret:
return err;
}
static cl_dynarray_err
corelibs_dynarray_get_cap_len(const cl_dynarray_t* arr, uintmax_t* save)
{
cl_dynarray_err err = CORELIBS_DYNARRAY_ERR_OK;
if (arr == NULL || save == NULL) {
err = CORELIBS_DYNARRAY_ERR_MEM_NULL;
goto ret;
}
*save = arr->cap.len;
ret:
return err;
}
static cl_dynarray_err
corelibs_dynarray_get_cap_lock(const cl_dynarray_t* arr, bool* save)
{
cl_dynarray_err err = CORELIBS_DYNARRAY_ERR_OK;
if (arr == NULL || save == NULL) {
err = CORELIBS_DYNARRAY_ERR_MEM_NULL;
goto ret;
}
*save = arr->cap.lock;
ret:
return err;
}
static cl_dynarray_err
corelibs_dynarray_get_size(const cl_dynarray_t* arr, size_t* save)
{
cl_dynarray_err err = CORELIBS_DYNARRAY_ERR_OK;
if (arr == NULL || save == NULL) {
err = CORELIBS_DYNARRAY_ERR_MEM_NULL;
goto ret;
}
*save = arr->es;
ret:
return err;
}
static cl_dynarray_err
corelibs_dynarray_cmp_data(const cl_dynarray_t* a, const cl_dynarray_t* b, bool* eq)
{
cl_dynarray_err err = CORELIBS_DYNARRAY_ERR_OK;
if (a == NULL || b == NULL) {
err = CORELIBS_DYNARRAY_ERR_MEM_NULL;
goto ret;
}
if (a->es != b->es) {
err = CORELIBS_DYNARRAY_ERR_VAR_NCOMPAT;
goto ret;
}
if (a->len != b->len) {
*eq = false;
goto ret;
}
*eq = (memcmp(a->addr, b->addr, a->len) != 0) ? false : true;
ret:
return err;
}
// Private functions
static cl_dynarray_err
corelibs_dynarray_bcheck(const cl_dynarray_t* arr, uintmax_t pos, uintmax_t len)
{
cl_dynarray_err err = CORELIBS_DYNARRAY_ERR_OK;
if (arr == NULL) {
err = CORELIBS_DYNARRAY_ERR_MEM_NULL;
goto ret;
}
if (arr->cap.len < pos + len) {
err = CORELIBS_DYNARRAY_ERR_MEM_OOB;
goto ret;
}
ret:
return err;
}