-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdict.h
211 lines (194 loc) · 6.82 KB
/
dict.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
/// @file
#pragma once
#include <stddef.h>
#include "arena.h"
typedef struct Dict Dict;
typedef struct DictItem DictItem;
typedef void *DictDataCopy(Arena *, void *, const void *, long); ///< Data copy function
static constexpr long dict_hash_shift = 2; ///< Number of hash bits to shift out
static constexpr long dict_branch_select = 64 - dict_hash_shift; ///< Bit mask to select branch
/**
* @brief Represents a dictionary of key-data pairs
*/
struct Dict {
Arena *arena; ///< Pointer to an arena allocator
struct {
long size; ///< Size of the item data in bytes
DictDataCopy *copy; ///< Pointer to a data copy function
} data; ///< Data properties
long length; ///< Number of items in the dict
DictItem *begin; ///< Pointer to root item of the dict
DictItem *end; ///< Pointer to last item of the dict
};
/**
* @brief Represents a single item of a dict
*/
struct DictItem {
struct {
void *data; ///< Pointer to the key data
long size; ///< Size of the key data in bytes
} key; ///< Key properties
void *data; ///< Pointer to the item data
DictItem *child[1 << dict_hash_shift]; ///< Array of child items for hash traversal
DictItem *next; ///< Pointer to the next item
};
/**
* @brief Iterate over all items of a dict
* @param item Current dict item
* @param self Pointer to a dict
*/
#define dict_for_each(item, self) \
for (auto(item) = (self)->begin; item; (item) = (item)->next) \
if ((item)->key.size)
/**
* @brief Create a new dict
* @param arena Pointer to an arena allocator
* @param size Size of item data in bytes (optional)
* @return New dict instance
* @note If `size == 0`, the data pointers will be directly assigned rather than copied
*/
static Dict dict_create(Arena *arena, long size) {
Dict dict = {};
dict.arena = arena;
dict.data.size = size;
dict.data.copy = arena_memcpy;
return dict;
}
/// @private
static uint64_t x__dict_hash_fnv1a(const char *key, long size) {
constexpr uint64_t basis = 0xcbf29ce484222325;
constexpr uint64_t prime = 0x00000100000001b3;
uint64_t hash = basis;
for (auto byte = key; byte < key + size; byte++) {
hash ^= *byte;
hash *= prime;
}
return hash;
}
/// @private
static bool x__dict_key_equals(const DictItem *item, const void *key, long size) {
return item->key.size == size && !memcmp(item->key.data, key, size);
}
/// @private
static void x__dict_item_init(const Dict *self, DictItem *item, const void *key, long size,
void *data) {
item->key.data = arena_memdup(self->arena, key, 1, size, alignof(max_align_t));
item->key.size = size;
if (data && self->data.size) {
item->data = arena_malloc(self->arena, 1, self->data.size, alignof(max_align_t));
self->data.copy(self->arena, item->data, data, self->data.size);
}
else {
item->data = data;
}
}
/**
* @brief Insert a new item into a dict
* @param self Pointer to a dict
* @param key Pointer to the key data
* @param size Size of the key data in bytes (optional)
* @param data Pointer to the item data
* @return Pointer to the item data if the key already exists, or `nullptr` if it is a new key
* @note If `size == 0`, `key` is assumed to be a null-terminated string
*/
static void *dict_insert(Dict *self, const void *key, long size, void *data) {
if (!size) {
size = strlen(key) + 1;
}
auto item = &self->begin;
for (auto hash = x__dict_hash_fnv1a(key, size); *item; hash <<= dict_hash_shift) {
if (!(*item)->key.size) {
break;
}
if (x__dict_key_equals(*item, key, size)) {
return (*item)->data;
}
item = &(*item)->child[hash >> dict_branch_select];
}
if (!*item) {
*item = arena_calloc(self->arena, 1, sizeof(DictItem), alignof(DictItem));
if (self->end) {
self->end->next = *item;
}
self->end = *item;
}
x__dict_item_init(self, *item, key, size, data);
self->length += 1;
return nullptr;
}
/**
* @brief Remove an item from a dict
* @param self Pointer to a dict
* @param key Pointer to the key data
* @param size Size of the key data in bytes (optional)
* @return Pointer to the item data, or `nullptr` if the key is not found
* @note If `size == 0`, `key` is assumed to be a null-terminated string
*/
static void *dict_remove(Dict *self, const void *key, long size) {
if (!size) {
size = strlen(key) + 1;
}
auto item = self->begin;
for (auto hash = x__dict_hash_fnv1a(key, size); item; hash <<= dict_hash_shift) {
if (x__dict_key_equals(item, key, size)) {
item->key.size = 0;
self->length -= 1;
return item->data;
}
item = item->child[hash >> dict_branch_select];
}
return nullptr;
}
/**
* @brief Find an item of a dict
* @param self Pointer to a dict
* @param key Pointer to the key data
* @param size Size of the key data in bytes (optional)
* @return Pointer to the item data, or `nullptr` if the key is not found
* @note If `size == 0`, `key` is assumed to be a null-terminated string
*/
static void *dict_find(const Dict *self, const void *key, long size) {
if (!size) {
size = strlen(key) + 1;
}
auto item = self->begin;
for (auto hash = x__dict_hash_fnv1a(key, size); item; hash <<= dict_hash_shift) {
if (x__dict_key_equals(item, key, size)) {
return item->data;
}
item = item->child[hash >> dict_branch_select];
}
return nullptr;
}
/**
* @brief Create a clone of a dict
* @param self Pointer to a dict
* @param arena Pointer to an arena allocator (optional)
* @return Cloned dict instance
* @note If no arena allocator is passed, the arena allocator of the dict is used
*/
static Dict dict_clone(const Dict *self, Arena *arena) {
Dict dict = {};
dict.arena = arena ? arena : self->arena;
dict.data = self->data;
dict_for_each(item, self) {
dict_insert(&dict, item->key.data, item->key.size, item->data);
}
return dict;
}
/**
* @brief Retrieve an array of dict items
* @param self Pointer to a dict
* @param arena Pointer to an arena allocator (optional)
* @return Pointer to an array of items
* @note If no arena allocator is passed, the arena allocator of the dict is used
*/
static DictItem *dict_items(const Dict *self, Arena *arena) {
arena = arena ? arena : self->arena;
DictItem *items = arena_malloc(arena, self->length, sizeof(DictItem), alignof(DictItem));
long index = 0;
dict_for_each(item, self) {
items[index++] = *item;
}
return items;
}