Logging in C with support for monoid logging
logging
jsonl
monoid
c
1#pragma once
2#include <assert.h>
3#include <stddef.h>
4#include <stdlib.h>
5#include <string.h>
6#include "prelude.h"
7
8/// @file dynarr.h
9/// @brief Type-safe dynamic array using a header-behind-pointer layout.
10///
11/// Each array is a single heap allocation: a DynArrHeader immediately followed
12/// by the element data. The pointer you hold points to the data region, so
13/// plain C array indexing (@c arr[i]) works directly.
14///
15/// @code
16/// int *nums = da_new(int);
17/// da_append(nums, 42);
18/// printf("%d\n", nums[0]);
19/// da_free(nums);
20/// @endcode
21///
22/// @note @p arr must always be a typed pointer variable — many macros may
23/// reassign it when a reallocation moves the underlying block.
24
25/// @brief Internal metadata stored immediately before the data pointer.
26typedef struct {
27 size_t count; ///< Number of elements currently stored.
28 size_t capacity; ///< Number of elements the allocation can hold.
29} DynArrHeader;
30
31/// @cond INTERNAL
32void* new_header(void);
33void* copy_header(void *arr, size_t elem_size);
34
35/// @brief Retrieve the DynArrHeader that precedes @p arr in memory.
36#define da_header(arr) ((DynArrHeader*)(arr) - 1)
37/// @endcond
38
39// ---------------------------------------------------------------------------
40// Lifecycle
41// ---------------------------------------------------------------------------
42
43/// @brief Allocate a new empty dynamic array.
44///
45/// @param type The element type (e.g. @c int, @c float, a struct). Used only
46/// as documentation; the initial allocation contains no elements.
47/// @return A @c void* pointing to the (empty) data region. Assign to a typed
48/// pointer: @code int *arr = da_new(int); @endcode
49/// @note Aborts on allocation failure.
50#define da_new(type) new_header()
51
52/// @brief Free a dynamic array.
53///
54/// Releases the entire allocation (header + data). @p arr must not be used
55/// after this call.
56///
57/// @param arr The array to free.
58#define da_free(arr) do { \
59 free(da_header(arr)); \
60 } while (0)
61
62/// @brief Return an independent copy of @p arr.
63///
64/// Allocates a fresh block and copies all elements into it. The copy is
65/// @e tight: its capacity equals its count. Mutations to either array do not
66/// affect the other.
67///
68/// @param arr The source array.
69/// @return A new @c void* pointing to the copied data. Assign to a typed
70/// pointer of the same type as @p arr.
71/// @note Aborts on allocation failure.
72#define da_copy(arr) copy_header((void*)(arr), sizeof(*(arr)))
73
74// ---------------------------------------------------------------------------
75// Adding elements
76// ---------------------------------------------------------------------------
77
78/// @brief Append a single element to the end of the array.
79///
80/// Grows the allocation when @c count == @c capacity. Initial capacity is 8;
81/// subsequent growth adds 50% (@c capacity + @c capacity/2).
82///
83/// @param arr The array. May be reassigned if a reallocation occurs.
84/// @param elem The value to append (evaluated once).
85/// @note Aborts if @p arr is @c NULL or if reallocation fails.
86#define da_append(arr, elem) do { \
87 exists(arr); \
88 DynArrHeader* h = da_header(arr); \
89 if (h->count == h->capacity) { \
90 size_t new_cap = h->capacity == 0 ? 8 : h->capacity + h->capacity / 2; \
91 void*tmp = realloc(h, sizeof(DynArrHeader) + new_cap * sizeof(elem)); \
92 exists(tmp); \
93 h = tmp; \
94 h->capacity = new_cap; \
95 } \
96 arr = (void*)(h + 1); \
97 arr[h->count++] = elem; \
98 } while (0)
99
100/// @brief Append @p n elements from a plain C array.
101///
102/// Equivalent to calling da_append() in a loop but more efficient: at most
103/// one reallocation is performed.
104///
105/// @param arr The destination array. May be reassigned if a reallocation occurs.
106/// @param ptr Pointer to the source elements.
107/// @param n Number of elements to copy from @p ptr.
108/// @note Aborts on allocation failure.
109#define da_extend(arr, ptr, n) do { \
110 size_t _n = (n); \
111 size_t _count = da_header(arr)->count; \
112 da_reserve(arr, _count + _n); \
113 memcpy((arr) + _count, (ptr), _n * sizeof(*(arr))); \
114 da_header(arr)->count = _count + _n; \
115 } while (0)
116
117/// @brief Pre-allocate capacity for at least @p n elements.
118///
119/// Ensures that at least @p n elements can be stored without further
120/// reallocation. A no-op if current capacity already meets or exceeds @p n.
121///
122/// @param arr The array. May be reassigned if a reallocation occurs.
123/// @param n Minimum desired capacity.
124/// @note Aborts on allocation failure.
125#define da_reserve(arr, n) do { \
126 DynArrHeader* h = da_header(arr); \
127 if ((n) > h->capacity) { \
128 void* tmp = realloc(h, sizeof(DynArrHeader) + (n) * sizeof(*(arr))); \
129 exists(tmp); \
130 h = tmp; \
131 h->capacity = (n); \
132 arr = (void*)(h + 1); \
133 } \
134 } while (0)
135
136// ---------------------------------------------------------------------------
137// Removing elements
138// ---------------------------------------------------------------------------
139
140/// @brief Remove and return the last element.
141///
142/// Decrements @c count and returns the value that was at @c arr[count].
143/// Capacity is unchanged.
144///
145/// @param arr The array.
146/// @return The removed element (lvalue).
147#define da_pop(arr) (arr)[--da_header(arr)->count]
148
149/// @brief O(1) unordered removal at index @p i.
150///
151/// Overwrites @c arr[i] with the last element then decrements @c count.
152/// Does @e not preserve element order. Use da_last() to read the element
153/// before removal if you need its value.
154///
155/// @param arr The array.
156/// @param i Index to remove. Bounds-checked with @c assert.
157#define da_swap_remove(arr, i) do { \
158 DynArrHeader* h = da_header(arr); \
159 assert((i) < h->count); \
160 (arr)[(i)] = (arr)[h->count - 1]; \
161 h->count--; \
162 } while (0)
163
164/// @brief Reset the element count to zero, keeping the allocation intact.
165///
166/// After a clear, da_empty() returns true and the buffer can be reused
167/// without another allocation.
168///
169/// @param arr The array.
170#define da_clear(arr) do { da_header(arr)->count = 0; } while (0)
171
172// ---------------------------------------------------------------------------
173// Accessing elements
174// ---------------------------------------------------------------------------
175
176/// @brief Bounds-checked element access via @c assert.
177///
178/// @param arr The array.
179/// @param i Index (must be < da_length(@p arr)).
180/// @return The element at index @p i (lvalue).
181#define da_get(arr, i) (assert((i) < da_header(arr)->count), (arr)[(i)])
182
183/// @brief The last element (no bounds check).
184///
185/// @param arr The array. Must be non-empty.
186/// @return The element at @c arr[count - 1] (lvalue).
187#define da_last(arr) (arr)[da_header(arr)->count - 1]
188
189// ---------------------------------------------------------------------------
190// Inspecting state
191// ---------------------------------------------------------------------------
192
193/// @brief Number of elements currently stored.
194/// @param arr The array.
195/// @return @c count as @c size_t.
196#define da_length(arr) da_header(arr)->count
197
198/// @brief Current allocated capacity (elements, not bytes).
199/// @param arr The array.
200/// @return @c capacity as @c size_t.
201#define da_capacity(arr) da_header(arr)->capacity
202
203/// @brief Test whether the array has no elements.
204/// @param arr The array.
205/// @return @c 1 if @c count == 0, @c 0 otherwise.
206#define da_empty(arr) (da_header(arr)->count == 0)