Compare commits

...

9 Commits

Author SHA1 Message Date
Aryadev Chavali
6353268c7b prick_sv: PRICK_SHORTHAND 2026-03-17 21:16:41 +00:00
Aryadev Chavali
bdd5b5c5a8 prick_darr: PRICK_SHORTHAND 2026-03-17 21:16:32 +00:00
Aryadev Chavali
3bebc86991 prick_btree: PRICK_SHORTHAND 2026-03-17 21:08:21 +00:00
Aryadev Chavali
192efb5aef prick_sv|prick_btree: some tasks 2026-03-17 21:05:38 +00:00
Aryadev Chavali
88cfe77b5f prick_arena: shorthand 2026-03-17 21:04:31 +00:00
Aryadev Chavali
65c424530f prick_arena: major bug fixes and namespacing 2026-03-17 20:59:48 +00:00
Aryadev Chavali
8a3ae735dc prick_vec: PRICK_SHORTHAND quality of life feature
A little QoL feature implemented as a preprocesser flag, where we
provide macros without the `prick_` prefix that link to the prick_vec
functions.
2026-03-17 20:36:48 +00:00
Aryadev Chavali
277606d483 prick_vec: added vec_pop and vec_find 2026-03-17 20:36:39 +00:00
Aryadev Chavali
ead8983ded deleted prick.org and split TODOs into header files
Makes more sense - any users of libraries should be aware of what
tasks are to be done, even if they don't have access to the repo
itself.
2026-03-17 20:28:28 +00:00
6 changed files with 224 additions and 86 deletions

View File

@@ -1,18 +0,0 @@
#+title: Prick tasks
#+author: Aryadev Chavali
#+date: 2025-12-11
#+filetags: c prick
* prick_darr :prick_darr:
** TODO Use custom allocator
Allow users to provide custom allocator functions. They need to
provide:
- alloc
- realloc
- free
* prick_btree :prick_btree:
** TODO Pack custom user functions into their own structure
The allocation/print routines should be in their own structure which
the user passes in.
This means we don't need to have a prick_btree_t structure at all.

View File

@@ -9,6 +9,11 @@
#include "prick_arena.h"
in one of your code units.
To remove the `prick_` namespacing, please put:
#define PRICK_SHORTHAND
in any files before including prick_arena.h. Standard preprocesser rules apply
with regards to hierarchy.
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.
@@ -25,28 +30,30 @@
#include <stdint.h>
typedef struct Region
// A region is a fixed sized allocation on the heap. Allocations against a
// region will depend on the difference between `size` and `capacity` and may
// fail if there isn't enough free space. Regions are arranged in a linked list
// to allow for recursion on failure to allocate.
typedef struct PrickArenaRegion
{
struct Region *next;
struct PrickArenaRegion *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);
} prick_arena_region_t;
// An arena is simply a linked list of regions.
typedef struct
{
region_t *beg, *end;
} arena_t;
prick_arena_region_t *beg, *end;
} prick_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);
uint8_t *prick_arena_alloc(prick_arena_t *arena, uint32_t size);
uint8_t *prick_arena_realloc(prick_arena_t *arena, uint8_t *pointer,
uint32_t old_size, uint32_t new_size);
// Reset the arena but do not delete any regions, allowing for fresh allocations
// to take place.
void prick_arena_reset(prick_arena_t *arena);
void prick_arena_free(prick_arena_t *arena);
#ifdef PRICK_ARENA_IMPL
@@ -61,20 +68,26 @@ void arena_free(arena_t *arena);
#define REGION_CAPACITY_MULT 2
#endif
#ifndef MAX
#define MAX(A, B) ((A) > (B) ? (A) : (B))
#define MIN(A, B) ((A) < (B) ? (A) : (B))
#endif
region_t *region_make(uint32_t capacity, region_t *next)
#ifndef MIN
#define MIN(A, B) ((A) < (B) ? (A) : (B))
#endif
prick_arena_region_t *prick_region_make(uint32_t capacity,
prick_arena_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;
capacity = MAX(capacity, REGION_DEFAULT_SIZE);
prick_arena_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)
uint8_t *prick_region_alloc_rec(prick_arena_region_t *region, uint32_t capacity)
{
if (!region)
return NULL;
@@ -86,9 +99,10 @@ uint8_t *region_alloc_rec(region_t *region, uint32_t capacity)
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;
prick_arena_region_t *new =
prick_region_make(capacity * REGION_CAPACITY_MULT, NULL);
region->next = new;
region = new;
}
uint8_t *start = region->bytes + region->size;
@@ -96,86 +110,102 @@ uint8_t *region_alloc_rec(region_t *region, uint32_t capacity)
return start;
}
void region_delete_rec(region_t *region)
void prick_region_delete_rec(prick_arena_region_t *region)
{
for (region_t *next = NULL; region;
for (prick_arena_region_t *next = NULL; region;
next = region->next, free(region), region = next)
continue;
}
uint8_t *arena_alloc(arena_t *arena, uint32_t size)
uint8_t *prick_arena_alloc(prick_arena_t *arena, uint32_t size)
{
if (!arena->beg)
{
arena->beg = region_make(size, NULL);
arena->beg = prick_region_make(size, NULL);
arena->end = arena->beg;
}
uint8_t *start = region_alloc_rec(arena->beg, size);
uint8_t *start = prick_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)
uint8_t *prick_arena_realloc(prick_arena_t *arena, uint8_t *ptr,
uint32_t old_size, uint32_t new_size)
{
if (!pointer)
if (!ptr)
{
// 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;
return prick_arena_alloc(arena, new_size);
}
/*
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.
There are two options for reallocation strategy depending on where `ptr`
lies in its parent region (NOTE: certainly there can only be one parent
region):
1) If `ptr` lies at the end of the allocation and there is enough space to
fit the new_size requirement, just bump the parent region's size and
return the pointer (nearly "for free")
2) Otherwise, we must allocate a new block of memory and copy data over.
*/
if (ptr + oldsize == reg->bytes + reg->size &&
(reg->capacity - reg->size) > (newsize - oldsize))
// Linear search through our regions until we find parent region for `ptr`.
// FIXME: I'm pretty sure this is technically UB since we will almost
// certainly be comparing different batch allocations. Shame!
prick_arena_region_t *parent = NULL;
for (parent = arena->beg;
parent && !(parent->bytes <= ptr &&
parent->bytes + parent->size >= ptr + old_size);
parent = parent->next)
{
reg->size += newsize - oldsize;
continue;
}
// If `parent` is not NULL, then it is the region hosting `ptr`. Check if it
// satisfies the "lies at the end" condition.
if (parent && (ptr + old_size == parent->bytes + parent->size) &&
((parent->capacity - parent->size) > (new_size - old_size)))
{
parent->size += new_size - old_size;
return ptr;
}
arena_realloc__allocate_new:
new_ptr = arena_alloc(arena, newsize);
memcpy(new_ptr, ptr, oldsize);
// Otherwise, we'll need to reallocate.
uint8_t *new_ptr = prick_arena_alloc(arena, new_size);
memcpy(new_ptr, ptr, old_size);
return new_ptr;
}
void arena_reset(arena_t *arena)
void prick_arena_reset(prick_arena_t *arena)
{
for (region_t *region = arena->beg; region; region = region->next)
for (prick_arena_region_t *region = arena->beg; region; region = region->next)
{
region->size = 0;
memset(region->bytes, 0, region->capacity);
memset(region->bytes, 0, region->size);
}
}
void arena_free(arena_t *arena)
void prick_arena_free(prick_arena_t *arena)
{
region_delete_rec(arena->beg);
prick_region_delete_rec(arena->beg);
memset(arena, 0, sizeof(*arena));
}
#endif
#ifdef PRICK_SHORTHAND
typedef prick_arena_region_t arena_region_t;
typedef prick_arena_t arena_t;
#define arena_alloc prick_arena_alloc
#define arena_realloc prick_arena_realloc
#define arena_reset prick_arena_reset
#define arena_free prick_arena_free
#endif
#endif
/* Copyright (C) 2024 Aryadev Chavali

View File

@@ -9,8 +9,17 @@
#include "prick_btree.h"
in one of your code units.
To remove the `prick_` namespacing, please put:
#define PRICK_SHORTHAND
in any files before including prick_btree.h. Standard preprocesser rules apply
with regards to hierarchy.
An ordered binary tree implementation, allowing the use of custom comparators
and allocators.
Tasks:
- TODO: Pack user custom functions (allocate, comparison, etc) into a
structure.
*/
#ifndef PRICK_BTREE_H
@@ -143,6 +152,23 @@ void prick_btree_print(prick_bnode_t *root, FILE *fp,
#endif
#ifdef PRICK_SHORTHAND
typedef prick_bnode_t bnode_t;
typedef prick_btree_comp_fn btree_comp_fn;
typedef prick_btree_alloc_fn btree_alloc_fn;
typedef prick_btree_free_fn btree_free_fn;
typedef prick_btree_print_fn btree_print_fn;
#define btree_insert prick_btree_insert;
#define btree_right_rotate prick_btree_right_rotate;
#define btree_left_rotate prick_btree_left_rotate;
#define btree_print prick_btree_print;
#define btree_free prick_btree_free;
#endif
#endif
/* Copyright (C) 2025 Aryadev Chavali

View File

@@ -9,6 +9,11 @@
#include "prick_darr.h"
in one of your code units.
To remove the `prick_` namespacing, please put:
#define PRICK_SHORTHAND
in any files before including prick_darr.h. Standard preprocesser rules apply
with regards to hierarchy.
This library defines a dynamic array purely on the heap. Both the raw data for
the array as well as the metadata are in one allocation. Consumers of the
library will only ever need to deal with the former component i.e. they'll only
@@ -22,6 +27,9 @@
You may want to consider prick_vec.h if you want to more explicit control of
the dynamic array, and would like a stable pointer to the container itself.
Tasks:
- TODO: Implement ability to use a custom allocator.
*/
#ifndef PRICK_DARR_H
@@ -143,6 +151,21 @@ void prick_darr_clone(void **dest, void **src)
#endif
#ifdef PRICK_SHORTHAND
#endif
typedef prick_darr_t darr_t;
#define DARR_GET PRICK_DARR_GET
#define DARR_SIZE PRICK_DARR_SIZE
#define DARR_CAP PRICK_DARR_CAP
#define darr_make prick_darr_make
#define darr_free prick_darr_free
#define darr_append_byte prick_darr_append_byte
#define darr_append prick_darr_append
#define darr_clone prick_darr_clone
#define darr_ensure_remaining prick_darr_ensure_remaining
#endif
/* Copyright (C) 2024 Aryadev Chavali

View File

@@ -9,6 +9,11 @@
#include "prick_sv.h"
in one of your code units.
To remove the `prick_` namespacing, please put:
#define PRICK_SHORTHAND
in any files before including prick_sv.h. Standard preprocesser rules apply
with regards to hierarchy.
This is a simple read-only string view library. It defines some extremely
common functions you'd expect for a string view library, excluding any that
require allocation.
@@ -100,6 +105,22 @@ prick_sv_t prick_sv_while(prick_sv_t sv, const char *accept)
#endif
#ifdef PRICK_SHORTHAND
typedef prick_sv_t sv_t;
#define SV PRICK_SV
#define SV_AUTO PRICK_SV_AUTO
#define SV_FMT PRICK_SV_FMT
#define PR_SV PR_PRICK_SV
#define sv_chop_left prick_sv_chop_left
#define sv_chop_right prick_sv_chop_right
#define sv_truncate prick_sv_truncate
#define sv_substr prick_sv_substr
#define sv_till prick_sv_till
#define sv_while prick_sv_while
#endif
#endif
/* Copyright (C) 2026 Aryadev Chavali

View File

@@ -9,6 +9,11 @@
#include "prick_vec.h"
in one of your code units.
To remove the `prick_` namespacing, please put:
#define PRICK_SHORTHAND
in any files before including prick_vec.h. Standard preprocesser rules apply
with regards to hierarchy.
This library defines another form of dynamically sized array as opposed to
prick_darr.h. This one is closer to the one classically implemented by most; a
structure with some metadata and a pointer to the raw buffer. This way,
@@ -20,7 +25,10 @@
PRICK_VEC_INLINE_CAPACITY). This makes lookup _even faster_ (no derefence and
possibility of the entire vector existing in the CPU cache) and allows us to
avoid allocation for smaller use cases. If the number of elements exceeds
PRICK_VEC_INLINE_CAPACITY, we utilise the allocator.
PRICK_VEC_INLINE_CAPACITY, we utilise the generic memory allocator (malloc).
Tasks:
- TODO: Provide generic allocator support.
*/
#ifndef PRICK_VEC_H
@@ -49,11 +57,13 @@ static_assert(sizeof(prick_vec_t) == 64,
void prick_vec_append(prick_vec_t *vec, const void *const ptr, uint64_t size);
void prick_vec_append_byte(prick_vec_t *vec, uint8_t byte);
void *prick_vec_data(prick_vec_t *vec);
uint8_t *prick_vec_data(prick_vec_t *vec);
void prick_vec_ensure_capacity(prick_vec_t *vec, uint64_t capacity);
void prick_vec_ensure_free(prick_vec_t *vec, uint64_t size);
void prick_vec_free(prick_vec_t *vec);
void prick_vec_clone(prick_vec_t *v2, prick_vec_t *v1);
void *prick_vec_pop(prick_vec_t *vec, size_t member_size);
size_t prick_vec_find(prick_vec_t *vec, void *ptr, size_t ptrsize);
#define PRICK_VEC_GET(VEC, INDEX, TYPE) (((TYPE *)prick_vec_data(VEC))[INDEX])
@@ -81,7 +91,7 @@ void prick_vec_append_byte(prick_vec_t *vec, uint8_t byte)
++vec->size;
}
void *prick_vec_data(prick_vec_t *vec)
uint8_t *prick_vec_data(prick_vec_t *vec)
{
if (!vec)
return NULL;
@@ -146,9 +156,55 @@ void prick_vec_clone(prick_vec_t *v2, prick_vec_t *v1)
prick_vec_append(v2, prick_vec_data(v1), v1->size);
}
void *prick_vec_pop(prick_vec_t *vec, size_t member_size)
{
if (vec->size < member_size)
{
return NULL;
}
vec->size -= member_size;
return prick_vec_data(vec) + vec->size;
}
size_t prick_vec_find(prick_vec_t *vec, void *ptr, size_t ptrsize)
{
if (vec->size < ptrsize)
{
return vec->size + 1;
}
uint8_t *base = prick_vec_data(vec);
for (size_t i = 0; i < vec->size; i += ptrsize)
{
void *member = base + i;
if (!memcmp(member, ptr, ptrsize))
{
return i;
}
}
return vec->size + 1;
}
#undef MAX
#endif
#ifdef PRICK_SHORTHAND
typedef prick_vec_t vec_t;
#define vec_append prick_vec_append
#define vec_append_byte prick_vec_append_byte
#define vec_data prick_vec_data
#define vec_ensure_capacity prick_vec_ensure_capacity
#define vec_ensure_free prick_vec_ensure_free
#define vec_free prick_vec_free
#define vec_clone prick_vec_clone
#define vec_pop prick_vec_pop
#define vec_find prick_vec_find
#define VEC_GET PRICK_VEC_GET
#endif
#endif
/* Copyright (C) 2026 Aryadev Chavali