Skip to content

Commit 753594a

Browse files
Refactor of BASIC scalar variable storage
1) Move from a linked list to hashmaps for each type 2) Tidy up tidwall hashmap, proper doxygen comments, and add support for passing udata to the allocator functions so that it can be used with BASIC's per-context buddy allocator 3) Vastly improve the readability of variable.c
1 parent feb44cf commit 753594a

File tree

10 files changed

+806
-717
lines changed

10 files changed

+806
-717
lines changed

include/basic.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@
2828

2929
#define basic_debug if (debug) dprintf
3030

31+
#define SEED0 0x12345678abcdefULL
32+
#define SEED1 0xfedcba9876543210ULL
33+
3134
#include "basic/defines.h"
3235
#include "basic/structs.h"
3336
#include "basic/context.h"

include/basic/context.h

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -128,17 +128,17 @@ typedef struct basic_ctx {
128128
*
129129
* Each index in this array corresponds to a specific depth in the call stack.
130130
*/
131-
struct ub_var_int* local_int_variables[MAX_CALL_STACK_DEPTH];
131+
struct hashmap* local_int_variables[MAX_CALL_STACK_DEPTH]; // ub_var_int*
132132

133133
/**
134134
* @brief Local string variable stack for function/procedure scopes.
135135
*/
136-
struct ub_var_string* local_string_variables[MAX_CALL_STACK_DEPTH];
136+
struct hashmap* local_string_variables[MAX_CALL_STACK_DEPTH]; // ub_var_string*
137137

138138
/**
139139
* @brief Local double (real) variable stack for function/procedure scopes.
140140
*/
141-
struct ub_var_double* local_double_variables[MAX_CALL_STACK_DEPTH];
141+
struct hashmap* local_double_variables[MAX_CALL_STACK_DEPTH]; // ub_var_double*
142142

143143
/**
144144
* @brief Call stack for return line numbers during function, procedure, or `GOSUB` calls.
@@ -215,17 +215,17 @@ typedef struct basic_ctx {
215215
*
216216
* Stores all globally scoped integer variables in the program.
217217
*/
218-
struct ub_var_int* int_variables;
218+
struct hashmap* int_variables; // ub_var_int*
219219

220220
/**
221221
* @brief Global string variable list.
222222
*/
223-
struct ub_var_string* str_variables;
223+
struct hashmap* str_variables; // ub_var_string*
224224

225225
/**
226226
* @brief Global double variable list.
227227
*/
228-
struct ub_var_double* double_variables;
228+
struct hashmap* double_variables; // ub_var_double*
229229

230230
/**
231231
* @brief Global integer array variable list.

include/basic/structs.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@ typedef struct ub_var_string {
6060
size_t name_length; ///< Length of the variable name
6161
char* value; ///< The value of the string variable
6262
bool global; ///< True if the variable is global, false if local
63-
struct ub_var_string* next; ///< Pointer to the next string variable (for chaining)
6463
} ub_var_string;
6564

6665
/**

include/basic/variable.h

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ const char* basic_get_string_variable(const char* var, struct basic_ctx* ctx);
4747
* @param value The value to set for the string variable.
4848
* @param ctx The current BASIC context.
4949
* @param local Boolean indicating whether the variable is local.
50-
* @param global Boolean indicating whether the variable is global.
50+
* @param propagate_global Boolean indicating whether the variable is propagated globally to child programs.
5151
*/
52-
void basic_set_string_variable(const char* var, const char* value, struct basic_ctx* ctx, bool local, bool global);
52+
void basic_set_string_variable(const char* var, const char* value, struct basic_ctx* ctx, bool local, bool propagate_global);
5353

5454
/**
5555
* @brief Set the value of a double (real) variable.
@@ -60,9 +60,9 @@ void basic_set_string_variable(const char* var, const char* value, struct basic_
6060
* @param value The value to set for the double variable.
6161
* @param ctx The current BASIC context.
6262
* @param local Boolean indicating whether the variable is local.
63-
* @param global Boolean indicating whether the variable is global.
63+
* @param propagate_global Boolean indicating whether the variable is propagated globally to child programs.
6464
*/
65-
void basic_set_double_variable(const char* var, const double value, struct basic_ctx* ctx, bool local, bool global);
65+
void basic_set_double_variable(const char* var, const double value, struct basic_ctx* ctx, bool local, bool propagate_global);
6666

6767
/**
6868
* @brief Set the value of an integer variable.
@@ -73,9 +73,9 @@ void basic_set_double_variable(const char* var, const double value, struct basic
7373
* @param value The value to set for the integer variable.
7474
* @param ctx The current BASIC context.
7575
* @param local Boolean indicating whether the variable is local.
76-
* @param global Boolean indicating whether the variable is global.
76+
* @param propagate_global Boolean indicating whether the variable is propagated globally to child programs.
7777
*/
78-
void basic_set_int_variable(const char* var, int64_t value, struct basic_ctx* ctx, bool local, bool global);
78+
void basic_set_int_variable(const char* var, int64_t value, struct basic_ctx* ctx, bool local, bool propagate_global);
7979

8080
/**
8181
* @brief Get the numeric value of a variable.

include/hashmap.h

Lines changed: 210 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
/**
22
* @file hashmap.h
3+
* @brief Open-addressed hashmap (Robin Hood), with allocator udata support.
4+
*
5+
* Updated by Craig Edwards, September 2025, to allow `udata` to be passed to
6+
* the allocator functions. This enables contextual/arena allocators (e.g., per
7+
* BASIC interpreter context) without altering external call sites.
8+
*
9+
* Based on tidwall hashmap, copyright 2020 Joshua J Baker. All rights
10+
* reserved. Use of this source code is governed by an MIT-style licence that
11+
* can be found in the LICENSE file. https://github.com/tidwall/hashmap.c
312
*/
4-
// Copyright 2020 Joshua J Baker. All rights reserved.
5-
// Use of this source code is governed by an MIT-style
6-
// license that can be found in the LICENSE file.
7-
8-
// https://github.com/tidwall/hashmap.c
9-
10-
#ifndef HASHMAP_H
11-
#define HASHMAP_H
13+
#pragma once
1214

1315
#include "kernel.h"
1416
#include <stdbool.h>
@@ -17,45 +19,215 @@
1719

1820
struct hashmap;
1921

20-
struct hashmap *hashmap_new(size_t elsize, size_t cap,
21-
uint64_t seed0, uint64_t seed1,
22-
uint64_t (*hash)(const void *item,
23-
uint64_t seed0, uint64_t seed1),
24-
int (*compare)(const void *a, const void *b,
25-
void *udata),
26-
void (*elfree)(const void *item),
27-
void *udata);
28-
struct hashmap *hashmap_new_with_allocator(
29-
void *(*malloc)(size_t),
30-
void *(*realloc)(void *, size_t),
31-
void (*free)(const void*),
32-
size_t elsize, size_t cap,
33-
uint64_t seed0, uint64_t seed1,
34-
uint64_t (*hash)(const void *item,
35-
uint64_t seed0, uint64_t seed1),
36-
int (*compare)(const void *a, const void *b,
37-
void *udata),
38-
void (*elfree)(const void *item),
39-
void *udata);
22+
/* --------------------------------------------------------------------------
23+
* Callback typedefs
24+
* -------------------------------------------------------------------------- */
25+
26+
/**
27+
* @brief Allocate a new block.
28+
* @param size Bytes requested.
29+
* @param udata User data pointer supplied at map construction.
30+
* @return Pointer to allocated block, or NULL on failure.
31+
*/
32+
typedef void *(*hashmap_allocator)(size_t size, void *udata);
33+
34+
/**
35+
* @brief Resize an existing block.
36+
* @param ptr Existing allocation (or NULL to behave like malloc).
37+
* @param size New size in bytes.
38+
* @param udata User data pointer supplied at map construction.
39+
* @return Pointer to (possibly moved) block, or NULL on failure.
40+
*/
41+
typedef void *(*hashmap_reallocator)(void *ptr, size_t size, void *udata);
42+
43+
/**
44+
* @brief Release an allocated block.
45+
* @param ptr Block to free (may be NULL).
46+
* @param udata User data pointer supplied at map construction.
47+
*/
48+
typedef void (*hashmap_releaser)(const void *ptr, void *udata);
49+
50+
/**
51+
* @brief Hash an item.
52+
* @param item Pointer to key/item.
53+
* @param seed0 First 64-bit seed.
54+
* @param seed1 Second 64-bit seed.
55+
* @return 64-bit hash value.
56+
*/
57+
typedef uint64_t (*hashmap_hash_fn)(const void *item, uint64_t seed0, uint64_t seed1);
58+
59+
/**
60+
* @brief Compare two items.
61+
* @param a First item.
62+
* @param b Second item.
63+
* @param udata User data pointer supplied at map construction.
64+
* @return <0 if a<b, 0 if equal, >0 if a>b.
65+
*/
66+
typedef int (*hashmap_compare_fn)(const void *a, const void *b, void *udata);
67+
68+
/**
69+
* @brief Optional element destructor for items stored by value.
70+
* @param item Element to dispose.
71+
*
72+
* Note: This is only for element-internal resources. The map storage itself is
73+
* freed by the map’s allocator callbacks.
74+
*/
75+
typedef void (*hashmap_elfree_fn)(const void *item, void *udata);
76+
77+
/**
78+
* @brief Iterator callback for scanning all items.
79+
* @param item Current element.
80+
* @param udata User data pointer supplied at call site.
81+
* @return true to continue, false to stop.
82+
*/
83+
typedef bool (*hashmap_iter_fn)(const void *item, void *udata);
84+
85+
86+
/* --------------------------------------------------------------------------
87+
* Creation / destruction
88+
* -------------------------------------------------------------------------- */
89+
90+
/**
91+
* @brief Create a new hashmap with standard kernel allocators.
92+
*
93+
* @param elsize Size in bytes of each element stored by the map.
94+
* @param cap Initial capacity hint (0 defaults to 16).
95+
* @param seed0 First 64-bit hash seed.
96+
* @param seed1 Second 64-bit hash seed.
97+
* @param hash Hash function for items.
98+
* @param compare Comparison function for items.
99+
* @param elfree Optional element destructor (may be NULL).
100+
* @param udata User data passed to `compare` (and may be used internally).
101+
* @return Handle to a new hashmap, or NULL on OOM.
102+
*/
103+
struct hashmap *hashmap_new(size_t elsize, size_t cap, uint64_t seed0, uint64_t seed1, hashmap_hash_fn hash, hashmap_compare_fn compare, hashmap_elfree_fn elfree, void *udata);
104+
105+
/**
106+
* @brief Create a new hashmap with custom allocators.
107+
*
108+
* @param _malloc Allocator callback (required).
109+
* @param _realloc Reallocator callback (required).
110+
* @param _free Releaser callback (required).
111+
* @param elsize Size in bytes of each element stored by the map.
112+
* @param cap Initial capacity hint (0 defaults to 16).
113+
* @param seed0 First 64-bit hash seed.
114+
* @param seed1 Second 64-bit hash seed.
115+
* @param hash Hash function for items.
116+
* @param compare Comparison function for items.
117+
* @param elfree Optional element destructor (may be NULL).
118+
* @param udata User data passed to allocator callbacks and `compare`.
119+
* @return Handle to a new hashmap, or NULL on OOM.
120+
*/
121+
struct hashmap *hashmap_new_with_allocator(hashmap_allocator _malloc, hashmap_reallocator _realloc, hashmap_releaser _free, size_t elsize, size_t cap, uint64_t seed0, uint64_t seed1, hashmap_hash_fn hash, hashmap_compare_fn compare, hashmap_elfree_fn elfree, void *udata);
122+
123+
/**
124+
* @brief Free a hashmap and its storage.
125+
*
126+
* Calls `elfree` for each element if provided, then releases internal buffers
127+
* via the map’s allocator callbacks.
128+
*
129+
* @param map Hashmap handle (may be NULL).
130+
*/
40131
void hashmap_free(struct hashmap *map);
132+
133+
/**
134+
* @brief Clear all items from the map.
135+
*
136+
* Each element is passed to `elfree` (if provided). If `update_cap` is true,
137+
* internal capacity is trimmed to the current number of buckets to avoid new
138+
* allocations on the clear.
139+
*
140+
* @param map Hashmap handle.
141+
* @param update_cap When true, shrink capacity to current bucket count.
142+
*/
41143
void hashmap_clear(struct hashmap *map, bool update_cap);
144+
145+
/**
146+
* @brief Get the number of stored items.
147+
* @param map Hashmap handle.
148+
* @return Item count.
149+
*/
42150
size_t hashmap_count(struct hashmap *map);
151+
152+
/**
153+
* @brief Test whether the previous `hashmap_set` failed due to OOM.
154+
* @param map Hashmap handle.
155+
* @return true if the last set operation ran out of memory.
156+
*/
43157
bool hashmap_oom(struct hashmap *map);
158+
159+
/**
160+
* @brief Look up an item by key.
161+
* @param map Hashmap handle.
162+
* @param item Key to search for.
163+
* @return Pointer to stored item, or NULL if not found.
164+
*/
44165
void *hashmap_get(struct hashmap *map, const void *item);
166+
167+
/**
168+
* @brief Insert or replace an item.
169+
* @param map Hashmap handle.
170+
* @param item Item to insert (by value).
171+
* @return Pointer to the replaced item if it existed, otherwise NULL.
172+
*
173+
* On allocation failure returns NULL and sets the sticky OOM flag
174+
* (see `hashmap_oom`).
175+
*/
45176
void *hashmap_set(struct hashmap *map, const void *item);
177+
178+
/**
179+
* @brief Remove an item by key.
180+
* @param map Hashmap handle.
181+
* @param item Key to delete.
182+
* @return Pointer to the removed item, or NULL if not found.
183+
*/
46184
void *hashmap_delete(struct hashmap *map, void *item);
185+
186+
/**
187+
* @brief Probe a raw bucket position.
188+
* @param map Hashmap handle.
189+
* @param position Bucket index (will be reduced modulo bucket count).
190+
* @return Pointer to stored item at that bucket, or NULL.
191+
*/
47192
void *hashmap_probe(struct hashmap *map, uint64_t position);
48-
bool hashmap_scan(struct hashmap *map,
49-
bool (*iter)(const void *item, void *udata), void *udata);
50-
bool hashmap_iter(struct hashmap *map, size_t *i, void **item);
51193

52-
uint64_t hashmap_sip(const void *data, size_t len,
53-
uint64_t seed0, uint64_t seed1);
54-
uint64_t hashmap_murmur(const void *data, size_t len,
55-
uint64_t seed0, uint64_t seed1);
194+
/**
195+
* @brief Iterate over every item in the map.
196+
* @param map Hashmap handle.
197+
* @param iter Callback invoked for each item; return false to stop early.
198+
* @param udata User data passed to `iter`.
199+
* @return false if iteration was stopped early, true otherwise.
200+
*/
201+
bool hashmap_scan(struct hashmap *map, hashmap_iter_fn iter, void *udata);
56202

203+
/**
204+
* @brief Cursor-based iterator over items.
205+
* @param map Hashmap handle.
206+
* @param i Cursor (initialise to 0; updated on each call).
207+
* @param item Out: pointer to the current stored item.
208+
* @return true if an item was produced; false if iteration is complete.
209+
*
210+
* Note: If `hashmap_delete` is called during iteration, the bucket layout may
211+
* change. Reset the cursor to 0 to restart iteration safely.
212+
*/
213+
bool hashmap_iter(struct hashmap *map, size_t *i, void **item);
57214

58-
// DEPRECATED: use `hashmap_new_with_allocator`
59-
void hashmap_set_allocator(void *(*malloc)(size_t), void (*free)(const void*));
215+
/**
216+
* @brief SipHash-2-4.
217+
* @param data Input buffer.
218+
* @param len Length in bytes.
219+
* @param seed0 First 64-bit seed.
220+
* @param seed1 Second 64-bit seed.
221+
* @return 64-bit hash value.
222+
*/
223+
uint64_t hashmap_sip(const void *data, size_t len, uint64_t seed0, uint64_t seed1);
60224

61-
#endif
225+
/**
226+
* @brief Murmur3_86_128 (folded to 64 bits).
227+
* @param data Input buffer.
228+
* @param len Length in bytes.
229+
* @param seed0 First 64-bit seed.
230+
* @param seed1 Second 64-bit seed.
231+
* @return 64-bit hash value.
232+
*/
233+
uint64_t hashmap_murmur(const void *data, size_t len, uint64_t seed0, uint64_t seed1);

0 commit comments

Comments
 (0)