Compare commits

...

21 Commits

Author SHA1 Message Date
Aryadev Chavali
324b1d2dd5 prick_btree: rearrange prick_bnode_print function arguments 2025-11-25 19:14:46 +00:00
Aryadev Chavali
9b7a5af6ec prick_btree: Add assertion to ensure all management functions aren't NULL 2025-11-25 19:08:00 +00:00
Aryadev Chavali
8783361010 prick_btree: add custom printing function for values
Something you can feed in to the init function to be used by the
printer functions.
2025-11-25 19:07:23 +00:00
Aryadev Chavali
0278ee8599 gitignore 2025-11-25 18:50:45 +00:00
Aryadev Chavali
d3c284f762 prick_btree: refactor for namespacing
everything should have a `prick_` prepended name.  Just makes sense.
2025-11-25 18:50:22 +00:00
Aryadev Chavali
3ca2f3fb2a prick_btree: provide a general btree_t structure
This structure stores our comparator and allocator functions, as well
as a root node, ensuring we're using the same functions on one tree.
Makes the API much cleaner to use.
2025-11-17 01:05:09 +00:00
Aryadev Chavali
6d91af65aa prick_btree
Still need to make it better imo
2025-11-17 00:56:28 +00:00
Aryadev Chavali
cb583d0277 types -> prick_aliases 2025-11-17 00:56:19 +00:00
Aryadev Chavali
50bd444d7e arena -> prick_arena 2025-11-17 00:56:19 +00:00
Aryadev Chavali
f4364f38d1 vec -> prick_darr.h 2025-11-17 00:56:19 +00:00
Aryadev Chavali
44d97efd54 Added README 2025-11-17 00:18:17 +00:00
Aryadev Chavali
c93d358b6a Add types.h for useful type aliases
So simple it's not got any functions.
2025-04-09 22:55:37 +01:00
Aryadev Chavali
4218fa3a2c Translate region_delete_rec while loop to for loop, minor edits 2025-04-09 22:52:58 +01:00
Aryadev Chavali
1dd0f8835c Fixed arena_realloc
So the real purpose of arena_realloc is to figure out if we can get
away with just adjusting the region that the pointer given resides in
so that we don't need to _actually_ allocate any new memory.

The previous implementation did this in the special case where the
pointer given _is_ the entire region.  But we can adjust the region's
size _if_ the pointer given is the last allocation on the region
i.e. it's on the tail end.
2025-04-09 22:49:21 +01:00
Aryadev Chavali
1b78493b19 Completely document arena's functions and make region public
If someone uses the arena functionality, they may as well get the
region functionality for free.  In particular, they may just want a
fixed bump allocator implementation which they can get here for free.
2024-11-01 08:16:03 +00:00
Aryadev Chavali
9427c3d324 Implement arena_reset 2024-11-01 08:15:46 +00:00
Aryadev Chavali
cf93eede6b region_delete->region_delete_rec and slightly rework arena_realloc
1) Better naming.

2) Use MIN macro for choosing the size to copy over from the old
buffer to the new one.  Also fix issue where we free the old region of
memory before copying it over to the new buffer.
2024-11-01 08:13:11 +00:00
Aryadev Chavali
46f62c5d2c Simple arena implementation using singly linked list
Manages individual allocations via a bump allocator (region), with
unfit allocations triggering a new region allocation that is added to
a linked list.  Any new region allocations will always be oversized
for the initially requested size, to amortize the cost of future
allocations.
2024-11-01 07:48:19 +00:00
Aryadev Chavali
f0f1e06e1e VEC_MULT may be set as user, so have a #if guard. 2024-10-24 03:17:54 +01:00
Aryadev Chavali
3a8f0c8d00 Use VEC_MULT in vec_ensure_remaining 2024-10-24 03:13:14 +01:00
Aryadev Chavali
14dea7c48c Clean up macros in vec.h 2024-10-24 03:13:03 +01:00
7 changed files with 598 additions and 113 deletions

3
.gitignore vendored
View File

@@ -1,2 +1,3 @@
TAGS
main*
main*
*.sh

21
README.org Normal file
View File

@@ -0,0 +1,21 @@
#+begin_example
┌──────────────────────────────────┐
│ _____ _____ _____ _____ _ __ │
│ | __ \| __ \|_ _/ ____| |/ / │
│ | |__) | |__) | | || | | ' / │
│ | ___/| _ / | || | | < │
│ | | | | \ \ _| || |____| . \ │
│ |_| |_| \_\_____\_____|_|\_\ │
└──────────────────────────────────┘
#+end_example
A set of STB-style header-only libraries for common data structures
and algorithms that I may require while working on a C code base. The
idea is to be as close to "plug-and-play" as possible.
All you need to do is copy the relevant header files over to your
project, then setup the implementation code within one code unit by:
#+begin_src c
#define <LIB>_IMPL
#include "./<lib.h>"
#+end_src

44
prick_aliases.h Normal file
View File

@@ -0,0 +1,44 @@
/* prick_aliases.h:
* Created: 2025-04-09
* Author: Aryadev Chavali
* License: See end of file
* Commentary:
This library defines some useful aliases for common types. These are mostly
handpicked, and aren't necessary.
*/
#ifndef PRICK_ALIASES_H
#define PRICK_ALIASES_H
#include <assert.h>
#include <stdint.h>
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;
typedef int8_t i8;
typedef int16_t i16;
typedef int32_t i32;
typedef int64_t i64;
static_assert(sizeof(float) == 4, "f32 requires 4 byte floats");
static_assert(sizeof(double) == 8, "f64 requires 8 byte doubles");
typedef float f32;
typedef double f64;
#endif
/* Copyright (C) 2025 Aryadev Chavali
* This program 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 Unlicense for details.
* You may distribute and modify this code under the terms of the Unlicense,
* which you should have received a copy of along with this program. If not,
* please go to <https://unlicense.org/>.
*/

191
prick_arena.h Normal file
View File

@@ -0,0 +1,191 @@
/* prick_arena.h: An arena implementation.
* Created: 2024-11-01
* Author: Aryadev Chavali
* License: see end of file
* Commentary:
To utilise this library, please put:
#define PRICK_ARENA_IMPL
#include "prick_arena.h"
in one of your code units.
This library defines both:
- A simple bump allocator for regions, with the ability to attach more regions
via a linked list in case the current region when the current region is full.
- A simple arena memory allocator, using the bump allocator for its regions.
The regions of the arena are arranged in a linked list for simplicity. As
regions aren't reallocated (instead, a new region is generated), they are
stable pointers, and may be used throughout a program as long as the underlying
arena isn't deleted.
*/
#ifndef PRICK_ARENA_H
#define PRICK_ARENA_H
#include <stdint.h>
typedef struct Region
{
struct Region *next;
uint32_t size, capacity;
uint8_t bytes[];
} region_t;
region_t *region_make(uint32_t capacity, region_t *next);
uint8_t *region_alloc_flat(region_t *region, uint32_t size);
uint8_t *region_alloc_rec(region_t *region, uint32_t size);
void region_delete_rec(region_t *region);
typedef struct
{
region_t *beg, *end;
} arena_t;
uint8_t *arena_alloc(arena_t *arena, uint32_t size);
uint8_t *arena_realloc(arena_t *arena, uint8_t *pointer, uint32_t old_size,
uint32_t new_size);
void arena_reset(arena_t *arena);
void arena_free(arena_t *arena);
#ifdef PRICK_ARENA_IMPL
#include <malloc.h>
#include <string.h>
#ifndef REGION_DEFAULT_SIZE
#define REGION_DEFAULT_SIZE 512
#endif
#ifndef REGION_CAPACITY_MULT
#define REGION_CAPACITY_MULT 2
#endif
#define MAX(A, B) ((A) > (B) ? (A) : (B))
#define MIN(A, B) ((A) < (B) ? (A) : (B))
region_t *region_make(uint32_t capacity, region_t *next)
{
capacity = MAX(capacity, REGION_DEFAULT_SIZE);
region_t *region = calloc(1, sizeof(*region) + capacity);
region->next = next;
region->size = 0;
region->capacity = capacity;
return region;
}
uint8_t *region_alloc_rec(region_t *region, uint32_t capacity)
{
if (!region)
return NULL;
for (; region->next && region->capacity - region->size < capacity;
region = region->next)
continue;
if (region->capacity - region->size < capacity)
{
// no region->next, so make a new region that can fit the capacity required.
region_t *new = region_make(capacity * REGION_CAPACITY_MULT, NULL);
region->next = new;
region = new;
}
uint8_t *start = region->bytes + region->size;
region->size += capacity;
return start;
}
void region_delete_rec(region_t *region)
{
for (region_t *next = NULL; region;
next = region->next, free(region), region = next)
continue;
}
uint8_t *arena_alloc(arena_t *arena, uint32_t size)
{
if (!arena->beg)
{
arena->beg = region_make(size, NULL);
arena->end = arena->beg;
}
uint8_t *start = region_alloc_rec(arena->beg, size);
// if we've attached a new region, end needs to be at that region
if (arena->end->next)
arena->end = arena->end->next;
return start;
}
uint8_t *arena_realloc(arena_t *arena, uint8_t *pointer, uint32_t old_size,
uint32_t new_size)
{
if (!pointer)
// Basically the same as allocating at this point
return arena_alloc(arena, newsize);
// Firstly find the region the pointer exists in
region_t *prev, *reg;
for (prev = NULL, reg = arena->beg;
reg &&
!(reg->bytes <= pointer && reg->bytes + reg->size >= pointer + old_size);
prev = reg, reg = reg->next)
continue;
uint8_t *new_ptr = NULL;
// pointer isn't allocated in the arena, just allocate a new pointer
if (!reg)
goto arena_realloc__allocate_new;
/*
If `ptr` is the latest allocation in `reg` and `reg` has enough capacity to
handle newsize, then we can adjust `reg` and not have to do any further
allocation work.
This check is not UB because ptr is confirmed to be in reg.
*/
if (ptr + oldsize == reg->bytes + reg->size &&
(reg->capacity - reg->size) > (newsize - oldsize))
{
reg->size += newsize - oldsize;
return ptr;
}
arena_realloc__allocate_new:
new_ptr = arena_alloc(arena, newsize);
memcpy(new_ptr, ptr, oldsize);
return new_ptr;
}
void arena_reset(arena_t *arena)
{
for (region_t *region = arena->beg; region; region = region->next)
{
region->size = 0;
memset(region->bytes, 0, region->capacity);
}
}
void arena_free(arena_t *arena)
{
region_delete_rec(arena->beg);
memset(arena, 0, sizeof(*arena));
}
#endif
#endif
/* Copyright (C) 2024 Aryadev Chavali
* This program 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 Unlicense
* for details.
* You may distribute and modify this code under the terms of the
* Unlicense, which you should have received a copy of along with this
* program. If not, please go to <https://unlicense.org/>.
*/

210
prick_btree.h Normal file
View File

@@ -0,0 +1,210 @@
/* prick_btree.h: A generic ordered binary tree.
* Created: 2025-04-09
* Author: Aryadev Chavali
* License: See end of file
* Commentary:
To utilise this library, please put:
#define PRICK_BTREE_IMPL
#include "prick_btree.h"
in one of your code units.
An ordered binary tree implementation, allowing the use of custom comparators
and allocators.
*/
#ifndef PRICK_BTREE_H
#define PRICK_BTREE_H
#include <stdio.h>
typedef struct Prick_Bnode
{
void *value;
struct Prick_Bnode *left, *right;
} prick_bnode_t;
typedef int (*prick_bnode_comp_fn)(void *, void *);
typedef prick_bnode_t *(*prick_bnode_alloc_fn)();
typedef void (*prick_bnode_free_fn)(prick_bnode_t *);
typedef void (*prick_print_fn)(FILE *, void *);
typedef struct
{
prick_bnode_t *root;
prick_bnode_comp_fn comp;
prick_bnode_alloc_fn alloc;
prick_bnode_free_fn free;
prick_print_fn print;
} prick_btree_t;
void prick_btree_init(prick_btree_t *tree, prick_bnode_comp_fn comparator,
prick_bnode_alloc_fn allocator, prick_bnode_free_fn free,
prick_print_fn print);
prick_bnode_t *prick_btree_insert(prick_btree_t *tree, void *value);
void prick_btree_print(FILE *fp, prick_btree_t *tree);
void prick_btree_free(prick_btree_t *tree);
void prick_bnode_right_rotate(prick_bnode_t **node);
void prick_bnode_left_rotate(prick_bnode_t **node);
void prick_bnode_print(FILE *fp, prick_print_fn print, prick_bnode_t *root);
#ifdef PRICK_BTREE_IMPL
#include <assert.h>
#include <stdlib.h>
void prick_btree_init(prick_btree_t *tree, prick_bnode_comp_fn comparator,
prick_bnode_alloc_fn allocator, prick_bnode_free_fn free,
prick_print_fn print)
{
// NOTE: These NEED to be supplied.
assert(comparator);
assert(allocator);
assert(free);
assert(print);
if (tree)
{
tree->root = NULL;
tree->comp = comparator;
tree->alloc = allocator;
tree->free = free;
tree->print = print;
}
}
prick_bnode_t *prick_bnode_insert(prick_bnode_t *node, prick_btree_t *tree,
void *value)
{
if (!node)
{
node = tree->alloc();
node->value = value;
node->left = NULL;
node->right = NULL;
return node;
}
int comp = tree->comp(value, node->value);
prick_bnode_t **picked_node = NULL;
if (comp < 0)
picked_node = &node->left;
else
picked_node = &node->right;
if (*picked_node)
prick_bnode_insert(*picked_node, tree, value);
else
{
*picked_node = tree->alloc();
picked_node[0]->value = value;
picked_node[0]->left = NULL;
picked_node[0]->right = NULL;
}
return node;
}
prick_bnode_t *prick_btree_insert(prick_btree_t *tree, void *value)
{
tree->root = prick_bnode_insert(tree->root, tree, value);
return tree->root;
}
void prick_btree_print(FILE *fp, prick_btree_t *tree)
{
if (!tree->root)
{
fprintf(fp, "()");
}
else
{
prick_bnode_print(fp, tree->print, tree->root);
}
}
void prick_bnode_free(prick_bnode_t *bnode, prick_bnode_free_fn free_fn)
{
if (!bnode)
return;
prick_bnode_t *left = bnode->left;
prick_bnode_t *right = bnode->right;
free_fn(bnode);
prick_bnode_free(left, free_fn);
prick_bnode_free(right, free_fn);
}
void prick_btree_free(prick_btree_t *tree)
{
if (!tree)
return;
prick_bnode_free(tree->root, tree->free);
tree->root = NULL;
}
void prick_bnode_right_rotate(prick_bnode_t **node)
{
if (!node || !*node)
return;
prick_bnode_t *left = (*node)->left;
if (left)
{
(*node)->left = left->right;
left->right = *node;
*node = left;
}
}
void prick_bnode_left_rotate(prick_bnode_t **node)
{
if (!node || !*node)
return;
prick_bnode_t *right = (*node)->right;
if (right)
{
(*node)->right = right->left;
right->left = *node;
*node = right;
}
}
void prick_bnode_print(FILE *fp, prick_print_fn print, prick_bnode_t *root)
{
if (!root)
return;
fprintf(fp, "(");
print(fp, root->value);
if (root->left)
{
fprintf(fp, " l");
prick_bnode_print(fp, print, root->left);
}
if (root->right)
{
fprintf(fp, " r");
prick_bnode_print(fp, print, root->right);
}
fprintf(fp, ")");
}
#endif
#endif
/* Copyright (C) 2025 Aryadev Chavali
* This program 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 Unlicense
* for details.
* You may distribute and modify this code under the terms of the
* Unlicense, which you should have received a copy of along with this
* program. If not, please go to <https://unlicense.org/>.
*/

130
prick_darr.h Normal file
View File

@@ -0,0 +1,130 @@
/* prick_darr.h: A dynamically sized array, all on the heap.
* Created: 2024-10-01
* Author: Aryadev Chavali
* License: see end of file
* Commentary:
To utilise this library, please put:
#define PRICK_DARR_IMPL
#include "prick_darr.h"
in one of your code units.
This library defines a dynamic array purely on the heap. We split the one
dynamic array allocation into two parts: the metadata, and the actual data.
Consumers of the library will only ever need to deal with the latter component
i.e. they'll only ever have access to the data they require.
Unfortuntely this does mean that the underlying data pointer is _not_ stable
during capacity reallocation, as we're allocating a whole new pointer with the
correct size. Therefore, if you're expecting the array to grow during the
runtime of your program, you should ensure any pointers to components of it are
updated as they will otherwise be dangling.
*/
#ifndef PRICK_DARR_H
#define PRICK_DARR_H
#include <stdint.h>
typedef struct
{
uint32_t size, capacity;
uint8_t bytes[];
} darr_t;
#define DARR_GET(P) (((darr_t *)(P)) - 1)
#define DARR_SIZE(P) (DARR_GET(P)->size)
#define DARR_CAP(P) (DARR_GET(P)->capacity)
void darr_make(void **ptr, uint32_t size);
void darr_free(void **ptr);
void darr_append_byte(void **ptr, uint8_t byte);
void darr_append(void **ptr, void *data, uint32_t size);
void darr_clone(void **dest, void **src);
void darr_ensure_remaining(void **ptr, uint32_t space);
#ifdef PRICK_DARR_IMPL
#include <malloc.h>
#include <string.h>
#ifndef DARR_MULT
#define DARR_MULT 2
#endif
#define MAX(A, B) ((A) > (B) ? (A) : (B))
#define MIN(A, B) ((A) < (B) ? (A) : (B))
void darr_make(void **ptr, uint32_t size)
{
if (!ptr)
return;
darr_t *darrtor = calloc(1, sizeof(*darrtor) + size);
darrtor->size = 0;
darrtor->capacity = size;
*ptr = (darrtor + 1);
}
void darr_free(void **data)
{
if (!data || !*data)
return;
free(DARR_GET(*data));
*data = NULL;
}
void darr_ensure_remaining(void **ptr, uint32_t space)
{
if (!ptr || !*ptr)
return;
darr_t *darr = DARR_GET(*ptr);
if (darr->capacity - darr->size < space)
{
void *new_darr = NULL;
darr_make(&new_darr, MAX(darr->capacity * DARR_MULT, darr->size + space));
DARR_SIZE(new_darr) = darr->size;
memcpy(new_darr, *ptr, darr->size);
darr_free(ptr);
*ptr = new_darr;
}
}
void darr_append_byte(void **ptr, uint8_t byte)
{
darr_ensure_remaining(ptr, 1);
darr_t *darr = DARR_GET(*ptr);
darr->bytes[darr->size++] = byte;
}
void darr_append(void **ptr, void *data, uint32_t size)
{
darr_ensure_remaining(ptr, size);
darr_t *darr = DARR_GET(*ptr);
memcpy(*ptr + darr->size, data, size);
darr->size += size;
}
void darr_clone(void **dest, void **src)
{
if (!dest || !src || !*src)
return;
darr_make(dest, DARR_SIZE(*src));
memcpy(*dest, *src, DARR_SIZE(*src));
DARR_SIZE(*dest) = DARR_SIZE(*src);
}
#endif
#endif
/* Copyright (C) 2024 Aryadev Chavali
* This program 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 Unlicense
* for details.
* You may distribute and modify this code under the terms of the
* Unlicense, which you should have received a copy of along with this
* program. If not, please go to <https://unlicense.org/>.
*/

112
vec.h
View File

@@ -1,112 +0,0 @@
/* Copyright (C) 2024 Aryadev Chavali
* This program 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 Unlicense
* for details.
* You may distribute and modify this code under the terms of the
* Unlicense, which you should have received a copy of along with this
* program. If not, please go to <https://unlicense.org/>.
* Created: 2024-10-01
* Author: Aryadev Chavali
* Description: A dynamically sized container.
*/
#ifndef VEC_H
#define VEC_H
#include <stdint.h>
typedef struct
{
uint32_t size, capacity;
uint8_t bytes[];
} vec_t;
#define VEC_GET(P) (((vec_t *)(P)) - 1)
#define VEC_SIZE(P) (VEC_GET(P)->size)
#define VEC_CAP(P) (VEC_GET(P)->capacity)
void vec_make(void **ptr, uint32_t size);
void vec_free(void **ptr);
void vec_append_byte(void **ptr, uint8_t byte);
void vec_append(void **ptr, void *data, uint32_t size);
void vec_clone(void **dest, void **src);
void vec_ensure_remaining(void **ptr, uint32_t space);
#define VEC_IMPL
#ifdef VEC_IMPL
#include <malloc.h>
#include <string.h>
#ifndef MAX
#define MAX(A, B) ((A) > (B) ? (A) : (B))
#endif
#ifndef MIN
#define MIN(A, B) ((A) < (B) ? (A) : (B))
#endif
void vec_make(void **ptr, uint32_t size)
{
if (!ptr)
return;
vec_t *vector = calloc(1, sizeof(*vector) + size);
vector->size = 0;
vector->capacity = size;
*ptr = (vector + 1);
}
void vec_free(void **data)
{
if (!data || !*data)
return;
free(VEC_GET(*data));
*data = NULL;
}
void vec_ensure_remaining(void **ptr, uint32_t space)
{
if (!ptr || !*ptr)
return;
vec_t *vec = VEC_GET(*ptr);
if (vec->capacity - vec->size < space)
{
void *new_vec = NULL;
vec_make(&new_vec, MAX(vec->capacity * 2, vec->size + space));
VEC_SIZE(new_vec) = vec->size;
memcpy(new_vec, *ptr, vec->size);
vec_free(ptr);
*ptr = new_vec;
}
}
void vec_append_byte(void **ptr, uint8_t byte)
{
vec_ensure_remaining(ptr, 1);
vec_t *vec = VEC_GET(*ptr);
vec->bytes[vec->size++] = byte;
}
void vec_append(void **ptr, void *data, uint32_t size)
{
vec_ensure_remaining(ptr, size);
vec_t *vec = VEC_GET(*ptr);
memcpy(vec->bytes + vec->size, data, size);
vec->size += size;
}
void vec_clone(void **dest, void **src)
{
if (!dest || !src || !*src)
return;
vec_make(dest, VEC_SIZE(*src));
memcpy(*dest, *src, VEC_SIZE(*src));
VEC_SIZE(*dest) = VEC_SIZE(*src);
}
#endif
#endif