natalie_parser 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +22 -0
- data/Dockerfile +26 -0
- data/Gemfile +10 -0
- data/LICENSE +21 -0
- data/README.md +55 -0
- data/Rakefile +242 -0
- data/ext/natalie_parser/extconf.rb +9 -0
- data/ext/natalie_parser/mri_creator.hpp +139 -0
- data/ext/natalie_parser/natalie_parser.cpp +144 -0
- data/include/natalie_parser/creator/debug_creator.hpp +113 -0
- data/include/natalie_parser/creator.hpp +108 -0
- data/include/natalie_parser/lexer/interpolated_string_lexer.hpp +64 -0
- data/include/natalie_parser/lexer/regexp_lexer.hpp +37 -0
- data/include/natalie_parser/lexer/word_array_lexer.hpp +57 -0
- data/include/natalie_parser/lexer.hpp +135 -0
- data/include/natalie_parser/node/alias_node.hpp +35 -0
- data/include/natalie_parser/node/arg_node.hpp +74 -0
- data/include/natalie_parser/node/array_node.hpp +34 -0
- data/include/natalie_parser/node/array_pattern_node.hpp +28 -0
- data/include/natalie_parser/node/assignment_node.hpp +34 -0
- data/include/natalie_parser/node/back_ref_node.hpp +28 -0
- data/include/natalie_parser/node/begin_block_node.hpp +25 -0
- data/include/natalie_parser/node/begin_node.hpp +52 -0
- data/include/natalie_parser/node/begin_rescue_node.hpp +47 -0
- data/include/natalie_parser/node/bignum_node.hpp +37 -0
- data/include/natalie_parser/node/block_node.hpp +55 -0
- data/include/natalie_parser/node/block_pass_node.hpp +33 -0
- data/include/natalie_parser/node/break_node.hpp +32 -0
- data/include/natalie_parser/node/call_node.hpp +85 -0
- data/include/natalie_parser/node/case_in_node.hpp +40 -0
- data/include/natalie_parser/node/case_node.hpp +52 -0
- data/include/natalie_parser/node/case_when_node.hpp +43 -0
- data/include/natalie_parser/node/class_node.hpp +39 -0
- data/include/natalie_parser/node/colon2_node.hpp +44 -0
- data/include/natalie_parser/node/colon3_node.hpp +34 -0
- data/include/natalie_parser/node/constant_node.hpp +26 -0
- data/include/natalie_parser/node/def_node.hpp +55 -0
- data/include/natalie_parser/node/defined_node.hpp +33 -0
- data/include/natalie_parser/node/encoding_node.hpp +26 -0
- data/include/natalie_parser/node/end_block_node.hpp +25 -0
- data/include/natalie_parser/node/evaluate_to_string_node.hpp +37 -0
- data/include/natalie_parser/node/false_node.hpp +23 -0
- data/include/natalie_parser/node/fixnum_node.hpp +36 -0
- data/include/natalie_parser/node/float_node.hpp +36 -0
- data/include/natalie_parser/node/hash_node.hpp +34 -0
- data/include/natalie_parser/node/hash_pattern_node.hpp +27 -0
- data/include/natalie_parser/node/identifier_node.hpp +123 -0
- data/include/natalie_parser/node/if_node.hpp +43 -0
- data/include/natalie_parser/node/infix_op_node.hpp +46 -0
- data/include/natalie_parser/node/interpolated_node.hpp +33 -0
- data/include/natalie_parser/node/interpolated_regexp_node.hpp +28 -0
- data/include/natalie_parser/node/interpolated_shell_node.hpp +22 -0
- data/include/natalie_parser/node/interpolated_string_node.hpp +31 -0
- data/include/natalie_parser/node/interpolated_symbol_key_node.hpp +18 -0
- data/include/natalie_parser/node/interpolated_symbol_node.hpp +28 -0
- data/include/natalie_parser/node/iter_node.hpp +45 -0
- data/include/natalie_parser/node/keyword_arg_node.hpp +25 -0
- data/include/natalie_parser/node/keyword_splat_node.hpp +38 -0
- data/include/natalie_parser/node/logical_and_node.hpp +40 -0
- data/include/natalie_parser/node/logical_or_node.hpp +40 -0
- data/include/natalie_parser/node/match_node.hpp +38 -0
- data/include/natalie_parser/node/module_node.hpp +32 -0
- data/include/natalie_parser/node/multiple_assignment_arg_node.hpp +32 -0
- data/include/natalie_parser/node/multiple_assignment_node.hpp +37 -0
- data/include/natalie_parser/node/next_node.hpp +37 -0
- data/include/natalie_parser/node/nil_node.hpp +23 -0
- data/include/natalie_parser/node/nil_sexp_node.hpp +23 -0
- data/include/natalie_parser/node/node.hpp +155 -0
- data/include/natalie_parser/node/node_with_args.hpp +47 -0
- data/include/natalie_parser/node/not_match_node.hpp +35 -0
- data/include/natalie_parser/node/not_node.hpp +37 -0
- data/include/natalie_parser/node/nth_ref_node.hpp +27 -0
- data/include/natalie_parser/node/op_assign_accessor_node.hpp +74 -0
- data/include/natalie_parser/node/op_assign_and_node.hpp +34 -0
- data/include/natalie_parser/node/op_assign_node.hpp +47 -0
- data/include/natalie_parser/node/op_assign_or_node.hpp +34 -0
- data/include/natalie_parser/node/pin_node.hpp +33 -0
- data/include/natalie_parser/node/range_node.hpp +52 -0
- data/include/natalie_parser/node/redo_node.hpp +20 -0
- data/include/natalie_parser/node/regexp_node.hpp +36 -0
- data/include/natalie_parser/node/retry_node.hpp +20 -0
- data/include/natalie_parser/node/return_node.hpp +34 -0
- data/include/natalie_parser/node/safe_call_node.hpp +31 -0
- data/include/natalie_parser/node/sclass_node.hpp +37 -0
- data/include/natalie_parser/node/self_node.hpp +23 -0
- data/include/natalie_parser/node/shadow_arg_node.hpp +40 -0
- data/include/natalie_parser/node/shell_node.hpp +32 -0
- data/include/natalie_parser/node/splat_node.hpp +39 -0
- data/include/natalie_parser/node/splat_value_node.hpp +32 -0
- data/include/natalie_parser/node/stabby_proc_node.hpp +29 -0
- data/include/natalie_parser/node/string_node.hpp +42 -0
- data/include/natalie_parser/node/super_node.hpp +44 -0
- data/include/natalie_parser/node/symbol_key_node.hpp +19 -0
- data/include/natalie_parser/node/symbol_node.hpp +30 -0
- data/include/natalie_parser/node/to_array_node.hpp +33 -0
- data/include/natalie_parser/node/true_node.hpp +23 -0
- data/include/natalie_parser/node/unary_op_node.hpp +41 -0
- data/include/natalie_parser/node/undef_node.hpp +31 -0
- data/include/natalie_parser/node/until_node.hpp +21 -0
- data/include/natalie_parser/node/while_node.hpp +52 -0
- data/include/natalie_parser/node/yield_node.hpp +29 -0
- data/include/natalie_parser/node.hpp +89 -0
- data/include/natalie_parser/parser.hpp +218 -0
- data/include/natalie_parser/token.hpp +842 -0
- data/include/tm/defer.hpp +34 -0
- data/include/tm/hashmap.hpp +826 -0
- data/include/tm/macros.hpp +16 -0
- data/include/tm/optional.hpp +223 -0
- data/include/tm/owned_ptr.hpp +186 -0
- data/include/tm/recursion_guard.hpp +156 -0
- data/include/tm/shared_ptr.hpp +259 -0
- data/include/tm/string.hpp +1447 -0
- data/include/tm/tests.hpp +78 -0
- data/include/tm/vector.hpp +796 -0
- data/lib/natalie_parser/sexp.rb +36 -0
- data/lib/natalie_parser/version.rb +5 -0
- data/lib/natalie_parser.rb +3 -0
- data/natalie_parser.gemspec +23 -0
- data/src/lexer/interpolated_string_lexer.cpp +88 -0
- data/src/lexer/regexp_lexer.cpp +95 -0
- data/src/lexer/word_array_lexer.cpp +134 -0
- data/src/lexer.cpp +1703 -0
- data/src/node/alias_node.cpp +11 -0
- data/src/node/assignment_node.cpp +33 -0
- data/src/node/begin_node.cpp +29 -0
- data/src/node/begin_rescue_node.cpp +33 -0
- data/src/node/class_node.cpp +22 -0
- data/src/node/interpolated_regexp_node.cpp +19 -0
- data/src/node/interpolated_shell_node.cpp +25 -0
- data/src/node/interpolated_string_node.cpp +111 -0
- data/src/node/interpolated_symbol_node.cpp +25 -0
- data/src/node/match_node.cpp +14 -0
- data/src/node/module_node.cpp +21 -0
- data/src/node/multiple_assignment_node.cpp +37 -0
- data/src/node/node.cpp +10 -0
- data/src/node/node_with_args.cpp +35 -0
- data/src/node/op_assign_node.cpp +36 -0
- data/src/node/string_node.cpp +33 -0
- data/src/parser.cpp +2972 -0
- data/src/token.cpp +27 -0
- metadata +186 -0
|
@@ -0,0 +1,826 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <algorithm>
|
|
4
|
+
#include <assert.h>
|
|
5
|
+
#include <math.h>
|
|
6
|
+
#include <stddef.h>
|
|
7
|
+
#include <stdint.h>
|
|
8
|
+
#include <stdio.h>
|
|
9
|
+
#include <string.h>
|
|
10
|
+
|
|
11
|
+
#include "tm/macros.hpp"
|
|
12
|
+
#include "tm/string.hpp"
|
|
13
|
+
|
|
14
|
+
namespace TM {
|
|
15
|
+
|
|
16
|
+
enum class HashType {
|
|
17
|
+
Pointer,
|
|
18
|
+
String,
|
|
19
|
+
TMString,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
struct HashmapUtils {
|
|
23
|
+
static size_t hashmap_hash_ptr(uintptr_t ptr) {
|
|
24
|
+
// https://stackoverflow.com/a/12996028/197498
|
|
25
|
+
auto x = (size_t)ptr;
|
|
26
|
+
x = (x ^ (x >> 30)) * UINT64_C(0xbf58476d1ce4e5b9);
|
|
27
|
+
x = (x ^ (x >> 27)) * UINT64_C(0x94d049bb133111eb);
|
|
28
|
+
x = x ^ (x >> 31);
|
|
29
|
+
return x;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
template <typename KeyT, typename T = void *>
|
|
34
|
+
class Hashmap {
|
|
35
|
+
public:
|
|
36
|
+
using HashFn = size_t(KeyT &);
|
|
37
|
+
using CompareFn = bool(KeyT &, KeyT &, void *);
|
|
38
|
+
|
|
39
|
+
static constexpr size_t HASHMAP_MIN_LOAD_FACTOR = 25;
|
|
40
|
+
static constexpr size_t HASHMAP_TARGET_LOAD_FACTOR = 50;
|
|
41
|
+
static constexpr size_t HASHMAP_MAX_LOAD_FACTOR = 75;
|
|
42
|
+
|
|
43
|
+
struct Item {
|
|
44
|
+
KeyT key;
|
|
45
|
+
T value;
|
|
46
|
+
size_t hash;
|
|
47
|
+
Item *next { nullptr };
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Constructs a Hashmap templated with key type and value type.
|
|
52
|
+
* Pass the hash function, compare function, and optionally,
|
|
53
|
+
* the initial capacity.
|
|
54
|
+
*
|
|
55
|
+
* ```
|
|
56
|
+
* auto hash_fn = &Hashmap<char*>::hash_str;
|
|
57
|
+
* auto compare_fn = &Hashmap<char*>::compare_str;
|
|
58
|
+
* auto map = Hashmap<char*, Thing>(hash_fn, compare_fn);
|
|
59
|
+
* auto key = strdup("foo");
|
|
60
|
+
* map.put(key, Thing(1));
|
|
61
|
+
* assert_eq(1, map.size());
|
|
62
|
+
* free(key);
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
Hashmap(HashFn hash_fn, CompareFn compare_fn, size_t initial_capacity = 10)
|
|
66
|
+
: m_capacity { calculate_map_size(initial_capacity) }
|
|
67
|
+
, m_hash_fn { hash_fn }
|
|
68
|
+
, m_compare_fn { compare_fn } { }
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Constructs a Hashmap templated with key type and value type.
|
|
72
|
+
* By default, the Hashmap is configured to hash pointer keys
|
|
73
|
+
* using a simple identity function.
|
|
74
|
+
*
|
|
75
|
+
* ```
|
|
76
|
+
* auto map = Hashmap<char*, Thing>();
|
|
77
|
+
* auto key1 = strdup("foo");
|
|
78
|
+
* auto key2 = strdup(key1); // different pointer
|
|
79
|
+
* map.put(key1, Thing(1));
|
|
80
|
+
* map.put(key2, Thing(2));
|
|
81
|
+
* assert_eq(2, map.size()); // two different keys
|
|
82
|
+
* free(key1);
|
|
83
|
+
* free(key2);
|
|
84
|
+
* ```
|
|
85
|
+
*
|
|
86
|
+
* To hash based on the C string's contents, then specify
|
|
87
|
+
* HashType::String.
|
|
88
|
+
*
|
|
89
|
+
* ```
|
|
90
|
+
* auto map = Hashmap<char*, Thing>(HashType::String);
|
|
91
|
+
* auto key1 = strdup("foo");
|
|
92
|
+
* auto key2 = strdup(key1); // different pointer
|
|
93
|
+
* map.put(key1, Thing(1));
|
|
94
|
+
* map.put(key2, Thing(2));
|
|
95
|
+
* assert_eq(1, map.size()); // same key
|
|
96
|
+
* free(key1);
|
|
97
|
+
* free(key2);
|
|
98
|
+
* ```
|
|
99
|
+
*
|
|
100
|
+
* When using a C string (char*) as the key, the char* is
|
|
101
|
+
* duplicated inside the Hashmap and managed for the lifetime
|
|
102
|
+
* of that key and/or hashmap. You do not need to keep
|
|
103
|
+
* the char* available or worry about freeing it.
|
|
104
|
+
*
|
|
105
|
+
* ```
|
|
106
|
+
* auto map = Hashmap<char*, Thing>(HashType::String);
|
|
107
|
+
* auto key = strdup("foo");
|
|
108
|
+
* map.put(key, Thing(1));
|
|
109
|
+
* free(key);
|
|
110
|
+
* auto key2 = strdup("foo");
|
|
111
|
+
* assert_eq(Thing(1), map.get(key2));
|
|
112
|
+
* free(key2);
|
|
113
|
+
* ```
|
|
114
|
+
*
|
|
115
|
+
* To use a TM::String as the key, specify HashType::TMString.
|
|
116
|
+
*
|
|
117
|
+
* ```
|
|
118
|
+
* auto map = Hashmap<String, Thing>(HashType::TMString);
|
|
119
|
+
* auto key1 = String("foo");
|
|
120
|
+
* auto key2 = String("foo");
|
|
121
|
+
* map.put(key1, Thing(1));
|
|
122
|
+
* map.put(key2, Thing(2));
|
|
123
|
+
* assert_eq(1, map.size()); // same key
|
|
124
|
+
* ```
|
|
125
|
+
*
|
|
126
|
+
* To use the Hashmap as a hash set (keys only), then you
|
|
127
|
+
* can specify only the key type and use set()/get().
|
|
128
|
+
*
|
|
129
|
+
* ```
|
|
130
|
+
* auto map = Hashmap<String>(HashType::TMString);
|
|
131
|
+
* map.set("foo");
|
|
132
|
+
* assert(map.get("foo"));
|
|
133
|
+
* ```
|
|
134
|
+
*/
|
|
135
|
+
Hashmap(HashType hash_type = HashType::Pointer, size_t initial_capacity = 10)
|
|
136
|
+
: m_capacity { calculate_map_size(initial_capacity) } {
|
|
137
|
+
switch (hash_type) {
|
|
138
|
+
case HashType::Pointer:
|
|
139
|
+
m_hash_fn = &Hashmap::hash_ptr;
|
|
140
|
+
m_compare_fn = &Hashmap::compare_ptr;
|
|
141
|
+
break;
|
|
142
|
+
case HashType::String:
|
|
143
|
+
m_hash_fn = &Hashmap::hash_str;
|
|
144
|
+
m_compare_fn = &Hashmap::compare_str;
|
|
145
|
+
break;
|
|
146
|
+
case HashType::TMString:
|
|
147
|
+
m_hash_fn = &Hashmap::hash_tm_str;
|
|
148
|
+
m_compare_fn = &Hashmap::compare_tm_str;
|
|
149
|
+
break;
|
|
150
|
+
default:
|
|
151
|
+
TM_UNREACHABLE();
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Returns a hash value for the given pointer as if it were
|
|
157
|
+
* just a 64-bit number. The contents of the pointer are
|
|
158
|
+
* not examined.
|
|
159
|
+
*
|
|
160
|
+
* ```
|
|
161
|
+
* auto key1 = strdup("foo");
|
|
162
|
+
* auto key2 = strdup("foo");
|
|
163
|
+
* assert_neq(
|
|
164
|
+
* Hashmap<char*>::hash_ptr(key1),
|
|
165
|
+
* Hashmap<char*>::hash_ptr(key2)
|
|
166
|
+
* );
|
|
167
|
+
* assert_eq(
|
|
168
|
+
* Hashmap<char*>::hash_ptr(key1),
|
|
169
|
+
* Hashmap<char*>::hash_ptr(key1)
|
|
170
|
+
* );
|
|
171
|
+
* free(key1);
|
|
172
|
+
* free(key2);
|
|
173
|
+
* ```
|
|
174
|
+
*/
|
|
175
|
+
static size_t hash_ptr(KeyT &ptr) {
|
|
176
|
+
if constexpr (std::is_pointer_v<KeyT>)
|
|
177
|
+
return HashmapUtils::hashmap_hash_ptr((uintptr_t)ptr);
|
|
178
|
+
else
|
|
179
|
+
return 0;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Returns true if the two given pointers are the same.
|
|
184
|
+
* The contents of the pointed-to object are not examined.
|
|
185
|
+
* Null must be passed as the third argument.
|
|
186
|
+
*
|
|
187
|
+
* ```
|
|
188
|
+
* auto key1 = strdup("foo");
|
|
189
|
+
* auto key2 = strdup("foo");
|
|
190
|
+
* assert_not(Hashmap<char*>::compare_ptr(key1, key2, nullptr));
|
|
191
|
+
* assert(Hashmap<char*>::compare_ptr(key1, key1, nullptr));
|
|
192
|
+
* free(key1);
|
|
193
|
+
* free(key2);
|
|
194
|
+
* ```
|
|
195
|
+
*/
|
|
196
|
+
static bool compare_ptr(KeyT &a, KeyT &b, void *) {
|
|
197
|
+
return a == b;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Returns a hash value for the given C string based on its contents.
|
|
202
|
+
*
|
|
203
|
+
* ```
|
|
204
|
+
* auto key1 = strdup("foo");
|
|
205
|
+
* auto key2 = strdup("foo");
|
|
206
|
+
* auto key3 = strdup("bar");
|
|
207
|
+
* assert_eq(
|
|
208
|
+
* Hashmap<char*>::hash_str(key1),
|
|
209
|
+
* Hashmap<char*>::hash_str(key2)
|
|
210
|
+
* );
|
|
211
|
+
* assert_neq(
|
|
212
|
+
* Hashmap<char*>::hash_ptr(key1),
|
|
213
|
+
* Hashmap<char*>::hash_ptr(key3)
|
|
214
|
+
* );
|
|
215
|
+
* free(key1);
|
|
216
|
+
* free(key2);
|
|
217
|
+
* free(key3);
|
|
218
|
+
* ```
|
|
219
|
+
*/
|
|
220
|
+
static size_t hash_str(KeyT &ptr) {
|
|
221
|
+
if constexpr (std::is_pointer_v<KeyT>) {
|
|
222
|
+
auto str = String((const char *)(ptr));
|
|
223
|
+
return str.djb2_hash();
|
|
224
|
+
} else {
|
|
225
|
+
return 0;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Returns true if the two given C strings have the same contents.
|
|
231
|
+
* Null must be passed as the third argument.
|
|
232
|
+
*
|
|
233
|
+
* ```
|
|
234
|
+
* auto key1 = strdup("foo");
|
|
235
|
+
* auto key2 = strdup("foo");
|
|
236
|
+
* auto key3 = strdup("bar");
|
|
237
|
+
* assert(Hashmap<char*>::compare_str(key1, key2, nullptr));
|
|
238
|
+
* assert_not(Hashmap<char*>::compare_str(key1, key3, nullptr));
|
|
239
|
+
* free(key1);
|
|
240
|
+
* free(key2);
|
|
241
|
+
* free(key3);
|
|
242
|
+
* ```
|
|
243
|
+
*/
|
|
244
|
+
static bool compare_str(KeyT &a, KeyT &b, void *) {
|
|
245
|
+
if constexpr (std::is_pointer_v<KeyT>)
|
|
246
|
+
return strcmp((const char *)(a), (const char *)(b)) == 0;
|
|
247
|
+
else
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Returns a hash value for the given TM::String based on its contents.
|
|
253
|
+
*
|
|
254
|
+
* ```
|
|
255
|
+
* auto key1 = String("foo");
|
|
256
|
+
* auto key2 = String("foo");
|
|
257
|
+
* auto key3 = String("bar");
|
|
258
|
+
* assert_eq(
|
|
259
|
+
* Hashmap<String>::hash_tm_str(key1),
|
|
260
|
+
* Hashmap<String>::hash_tm_str(key2)
|
|
261
|
+
* );
|
|
262
|
+
* assert_neq(
|
|
263
|
+
* Hashmap<String>::hash_tm_str(key1),
|
|
264
|
+
* Hashmap<String>::hash_tm_str(key3)
|
|
265
|
+
* );
|
|
266
|
+
* ```
|
|
267
|
+
*/
|
|
268
|
+
static size_t hash_tm_str(KeyT &ptr) {
|
|
269
|
+
if constexpr (std::is_pointer_v<KeyT>) {
|
|
270
|
+
return 0;
|
|
271
|
+
} else {
|
|
272
|
+
auto str = (const String &)(ptr);
|
|
273
|
+
return str.djb2_hash();
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Returns true if the two given TM:Strings have the same contents.
|
|
279
|
+
* Null must be passed as the third argument.
|
|
280
|
+
*
|
|
281
|
+
* ```
|
|
282
|
+
* auto key1 = String("foo");
|
|
283
|
+
* auto key2 = String("foo");
|
|
284
|
+
* auto key3 = String("bar");
|
|
285
|
+
* assert(Hashmap<String>::compare_tm_str(key1, key2, nullptr));
|
|
286
|
+
* assert_not(Hashmap<String>::compare_tm_str(key1, key3, nullptr));
|
|
287
|
+
* ```
|
|
288
|
+
*/
|
|
289
|
+
static bool compare_tm_str(KeyT &a, KeyT &b, void *) {
|
|
290
|
+
if constexpr (std::is_pointer_v<KeyT>) {
|
|
291
|
+
return false;
|
|
292
|
+
} else {
|
|
293
|
+
return ((const String &)a) == ((const String &)b);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Copies the given Hashmap.
|
|
299
|
+
*
|
|
300
|
+
* ```
|
|
301
|
+
* auto map1 = Hashmap<String, Thing>(HashType::TMString);
|
|
302
|
+
* map1.put("foo", Thing(1));
|
|
303
|
+
* auto map2 = Hashmap<String, Thing>(map1);
|
|
304
|
+
* assert_eq(Thing(1), map2.get("foo"));
|
|
305
|
+
* ```
|
|
306
|
+
*/
|
|
307
|
+
Hashmap(const Hashmap &other)
|
|
308
|
+
: m_capacity { other.m_capacity }
|
|
309
|
+
, m_hash_fn { other.m_hash_fn }
|
|
310
|
+
, m_compare_fn { other.m_compare_fn } {
|
|
311
|
+
m_map = new Item *[m_capacity] {};
|
|
312
|
+
copy_items_from(other);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Overwrites this Hashmap with another.
|
|
317
|
+
*
|
|
318
|
+
* ```
|
|
319
|
+
* auto map1 = Hashmap<String, Thing>(HashType::TMString);
|
|
320
|
+
* map1.put("foo", Thing(1));
|
|
321
|
+
* auto map2 = Hashmap<String, Thing>(HashType::TMString);
|
|
322
|
+
* map2.put("foo", Thing(2));
|
|
323
|
+
* map1 = map2;
|
|
324
|
+
* assert_eq(Thing(2), map1.get("foo"));
|
|
325
|
+
* ```
|
|
326
|
+
*/
|
|
327
|
+
Hashmap &operator=(const Hashmap &other) {
|
|
328
|
+
m_capacity = other.m_capacity;
|
|
329
|
+
m_hash_fn = other.m_hash_fn;
|
|
330
|
+
m_compare_fn = other.m_compare_fn;
|
|
331
|
+
if (m_map) {
|
|
332
|
+
clear();
|
|
333
|
+
delete[] m_map;
|
|
334
|
+
}
|
|
335
|
+
m_map = new Item *[m_capacity] {};
|
|
336
|
+
copy_items_from(other);
|
|
337
|
+
return *this;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Moves data from another Hashmap onto this one.
|
|
342
|
+
*
|
|
343
|
+
* ```
|
|
344
|
+
* auto map1 = Hashmap<String, Thing>(HashType::TMString);
|
|
345
|
+
* map1.put("foo", Thing(1));
|
|
346
|
+
* auto map2 = Hashmap<String, Thing>(HashType::TMString);
|
|
347
|
+
* map2.put("foo", Thing(2));
|
|
348
|
+
* map1 = std::move(map2);
|
|
349
|
+
* assert_eq(Thing(2), map1.get("foo"));
|
|
350
|
+
* assert_eq(0, map2.size());
|
|
351
|
+
* ```
|
|
352
|
+
*/
|
|
353
|
+
Hashmap &operator=(Hashmap &&other) {
|
|
354
|
+
if (m_map) {
|
|
355
|
+
clear();
|
|
356
|
+
delete[] m_map;
|
|
357
|
+
}
|
|
358
|
+
m_size = other.m_size;
|
|
359
|
+
m_capacity = other.m_capacity;
|
|
360
|
+
m_map = other.m_map;
|
|
361
|
+
m_hash_fn = other.m_hash_fn;
|
|
362
|
+
m_compare_fn = other.m_compare_fn;
|
|
363
|
+
other.m_size = 0;
|
|
364
|
+
other.m_capacity = 0;
|
|
365
|
+
other.m_map = nullptr;
|
|
366
|
+
return *this;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
~Hashmap() {
|
|
370
|
+
if (!m_map) return;
|
|
371
|
+
clear();
|
|
372
|
+
delete[] m_map;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Gets a value from the map stored under the given key.
|
|
377
|
+
* Optionally pass an additional 'data' pointer if your
|
|
378
|
+
* custom compare function requires the extra data
|
|
379
|
+
* parameter. (The built-in compare function does not
|
|
380
|
+
* use the data pointer.)
|
|
381
|
+
*
|
|
382
|
+
* ```
|
|
383
|
+
* auto map = Hashmap<String, Thing>(HashType::TMString);
|
|
384
|
+
* map.put("foo", Thing(1));
|
|
385
|
+
* assert_eq(Thing(1), map.get("foo"));
|
|
386
|
+
* ```
|
|
387
|
+
*
|
|
388
|
+
* If there is no value associated with the given key,
|
|
389
|
+
* then a default-constructed object is returned.
|
|
390
|
+
*
|
|
391
|
+
* ```
|
|
392
|
+
* auto map = Hashmap<String, Thing>(HashType::TMString);
|
|
393
|
+
* assert_eq(Thing(0), map.get("foo"));
|
|
394
|
+
* ```
|
|
395
|
+
*
|
|
396
|
+
* If there is no value associated with the given key,
|
|
397
|
+
* and your value type is a pointer type, then nullptr
|
|
398
|
+
* is returned.
|
|
399
|
+
*
|
|
400
|
+
* ```
|
|
401
|
+
* auto map = Hashmap<String, const char*>(HashType::TMString);
|
|
402
|
+
* assert_eq(nullptr, map.get("foo"));
|
|
403
|
+
* ```
|
|
404
|
+
*/
|
|
405
|
+
T get(KeyT key, void *data = nullptr) const {
|
|
406
|
+
auto hash = m_hash_fn(key);
|
|
407
|
+
auto item = find_item(key, hash, data);
|
|
408
|
+
if (item)
|
|
409
|
+
return item->value;
|
|
410
|
+
if constexpr (std::is_pointer_v<T>)
|
|
411
|
+
return nullptr;
|
|
412
|
+
else
|
|
413
|
+
return {};
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Finds and returns an Item* (the internal container type
|
|
418
|
+
* used by Hashmap) based on the given key and hash.
|
|
419
|
+
* Optionally pass a third data pointer if your custom
|
|
420
|
+
* compare function requires it.
|
|
421
|
+
*
|
|
422
|
+
* Use this method if you already have computed the hash value
|
|
423
|
+
* and/or need to access the internal Item structure directly.
|
|
424
|
+
*
|
|
425
|
+
* ```
|
|
426
|
+
* auto key = String("foo");
|
|
427
|
+
* auto map = Hashmap<String, Thing>(HashType::TMString);
|
|
428
|
+
* map.put(key, Thing(1));
|
|
429
|
+
* auto hash = Hashmap<String>::hash_tm_str(key);
|
|
430
|
+
* auto item = map.find_item(key, hash);
|
|
431
|
+
* assert_eq(Thing(1), item->value);
|
|
432
|
+
* ```
|
|
433
|
+
*
|
|
434
|
+
* If the item is not found, then null is returned.
|
|
435
|
+
*
|
|
436
|
+
* ```
|
|
437
|
+
* auto key = String("foo");
|
|
438
|
+
* auto map = Hashmap<String, Thing>(HashType::TMString);
|
|
439
|
+
* auto hash = Hashmap<String>::hash_tm_str(key);
|
|
440
|
+
* auto item = map.find_item(key, hash);
|
|
441
|
+
* assert_eq(nullptr, item);
|
|
442
|
+
* ```
|
|
443
|
+
*/
|
|
444
|
+
Item *find_item(KeyT key, size_t hash, void *data = nullptr) const {
|
|
445
|
+
if (m_size == 0) return nullptr;
|
|
446
|
+
assert(m_map);
|
|
447
|
+
auto index = index_for_hash(hash);
|
|
448
|
+
auto item = m_map[index];
|
|
449
|
+
while (item) {
|
|
450
|
+
if (hash == item->hash && m_compare_fn(key, item->key, data))
|
|
451
|
+
return item;
|
|
452
|
+
item = item->next;
|
|
453
|
+
}
|
|
454
|
+
return nullptr;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Sets a key in the Hashmap as if it were a hash set.
|
|
459
|
+
* Use this if you don't care about storing/retrieving values.
|
|
460
|
+
*
|
|
461
|
+
* ```
|
|
462
|
+
* auto map = Hashmap<String>(HashType::TMString);
|
|
463
|
+
* map.set("foo");
|
|
464
|
+
* assert(map.get("foo"));
|
|
465
|
+
* assert_not(map.get("bar"));
|
|
466
|
+
* ```
|
|
467
|
+
*/
|
|
468
|
+
void set(KeyT key) {
|
|
469
|
+
put(key, this); // we just put a placeholder value, a pointer to this Hashmap
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Puts the given value at the given key in the Hashmap.
|
|
474
|
+
*
|
|
475
|
+
* ```
|
|
476
|
+
* auto map = Hashmap<String, Thing>(HashType::TMString);
|
|
477
|
+
* map.put("foo", Thing(1));
|
|
478
|
+
* assert_eq(Thing(1), map.get("foo"));
|
|
479
|
+
* map.put("foo", Thing(2));
|
|
480
|
+
* assert_eq(Thing(2), map.get("foo"));
|
|
481
|
+
* ```
|
|
482
|
+
*
|
|
483
|
+
* If your custom compare function requires the additional 'data'
|
|
484
|
+
* pointer, then pass it as the third parameter.
|
|
485
|
+
*/
|
|
486
|
+
void put(KeyT key, T value, void *data = nullptr) {
|
|
487
|
+
if (!m_map)
|
|
488
|
+
m_map = new Item *[m_capacity] {};
|
|
489
|
+
if (load_factor() > HASHMAP_MAX_LOAD_FACTOR)
|
|
490
|
+
rehash();
|
|
491
|
+
auto hash = m_hash_fn(key);
|
|
492
|
+
Item *item;
|
|
493
|
+
if ((item = find_item(key, hash, data))) {
|
|
494
|
+
item->value = value;
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
auto index = index_for_hash(hash);
|
|
498
|
+
auto new_key = duplicate_key(key);
|
|
499
|
+
auto new_item = new Item { new_key, value, hash };
|
|
500
|
+
insert_item(m_map, index, new_item);
|
|
501
|
+
m_size++;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Removes and returns the value at the given key.
|
|
506
|
+
*
|
|
507
|
+
* ```
|
|
508
|
+
* auto map = Hashmap<String, Thing>(HashType::TMString);
|
|
509
|
+
* map.put("foo", Thing(1));
|
|
510
|
+
* assert_eq(Thing(1), map.remove("foo"));
|
|
511
|
+
* assert_eq(Thing(), map.remove("foo"));
|
|
512
|
+
* ```
|
|
513
|
+
*
|
|
514
|
+
* If there is no value associated with the given key,
|
|
515
|
+
* then a default-constructed object is returned.
|
|
516
|
+
*
|
|
517
|
+
* ```
|
|
518
|
+
* auto map = Hashmap<String, Thing>(HashType::TMString);
|
|
519
|
+
* assert_eq(Thing(0), map.remove("foo"));
|
|
520
|
+
* ```
|
|
521
|
+
*
|
|
522
|
+
* If there is no value associated with the given key,
|
|
523
|
+
* and your value type is a pointer type, then nullptr
|
|
524
|
+
* is returned.
|
|
525
|
+
*
|
|
526
|
+
* ```
|
|
527
|
+
* auto map = Hashmap<String, const char*>(HashType::TMString);
|
|
528
|
+
* assert_eq(nullptr, map.remove("foo"));
|
|
529
|
+
* ```
|
|
530
|
+
*/
|
|
531
|
+
T remove(KeyT key, void *data = nullptr) {
|
|
532
|
+
if (!m_map) {
|
|
533
|
+
if constexpr (std::is_pointer_v<T>)
|
|
534
|
+
return nullptr;
|
|
535
|
+
else
|
|
536
|
+
return {};
|
|
537
|
+
}
|
|
538
|
+
auto hash = m_hash_fn(key);
|
|
539
|
+
auto index = index_for_hash(hash);
|
|
540
|
+
auto item = m_map[index];
|
|
541
|
+
if (item) {
|
|
542
|
+
// m_map[index] = [item] -> item -> item
|
|
543
|
+
// ^ this one
|
|
544
|
+
if (hash == item->hash && m_compare_fn(key, item->key, data)) {
|
|
545
|
+
auto value = item->value;
|
|
546
|
+
delete_item(index, item);
|
|
547
|
+
return value;
|
|
548
|
+
}
|
|
549
|
+
auto chained_item = item->next;
|
|
550
|
+
while (chained_item) {
|
|
551
|
+
// m_map[index] = item -> [item] -> item
|
|
552
|
+
// ^ this one
|
|
553
|
+
if (hash == chained_item->hash && m_compare_fn(key, chained_item->key, data)) {
|
|
554
|
+
auto value = chained_item->value;
|
|
555
|
+
delete_item(item, chained_item);
|
|
556
|
+
return value;
|
|
557
|
+
}
|
|
558
|
+
item = chained_item;
|
|
559
|
+
chained_item = chained_item->next;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
if constexpr (std::is_pointer_v<T>)
|
|
563
|
+
return nullptr;
|
|
564
|
+
else
|
|
565
|
+
return {};
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* Removes all keys/values from the Hashmap.
|
|
570
|
+
*
|
|
571
|
+
* ```
|
|
572
|
+
* auto map = Hashmap<String, Thing>(HashType::TMString);
|
|
573
|
+
* map.put("foo", Thing(1));
|
|
574
|
+
* assert_eq(1, map.size());
|
|
575
|
+
* map.clear();
|
|
576
|
+
* assert_eq(0, map.size());
|
|
577
|
+
* ```
|
|
578
|
+
*/
|
|
579
|
+
void clear() {
|
|
580
|
+
if (!m_map) return;
|
|
581
|
+
for (size_t i = 0; i < m_capacity; i++) {
|
|
582
|
+
auto item = m_map[i];
|
|
583
|
+
m_map[i] = nullptr;
|
|
584
|
+
while (item) {
|
|
585
|
+
auto next_item = item->next;
|
|
586
|
+
free_key(item->key);
|
|
587
|
+
delete item;
|
|
588
|
+
item = next_item;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
m_size = 0;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* Returns the number of values stored in the Hashmap.
|
|
596
|
+
*
|
|
597
|
+
* ```
|
|
598
|
+
* auto map = Hashmap<String, Thing>(HashType::TMString);
|
|
599
|
+
* map.put("foo", Thing(1));
|
|
600
|
+
* assert_eq(1, map.size());
|
|
601
|
+
* ```
|
|
602
|
+
*/
|
|
603
|
+
size_t size() const { return m_size; }
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* Returns true if there are zero values stored in the Hashmap.
|
|
607
|
+
*
|
|
608
|
+
* ```
|
|
609
|
+
* auto map = Hashmap<String, Thing>(HashType::TMString);
|
|
610
|
+
* assert(map.is_empty());
|
|
611
|
+
* map.put("foo", Thing(1));
|
|
612
|
+
* assert_not(map.is_empty());
|
|
613
|
+
* ```
|
|
614
|
+
*/
|
|
615
|
+
bool is_empty() const { return m_size == 0; }
|
|
616
|
+
|
|
617
|
+
class iterator {
|
|
618
|
+
public:
|
|
619
|
+
iterator(Hashmap &hashmap, size_t index, Item *item)
|
|
620
|
+
: m_hashmap { hashmap }
|
|
621
|
+
, m_index { index }
|
|
622
|
+
, m_item { item } { }
|
|
623
|
+
|
|
624
|
+
iterator &operator++() {
|
|
625
|
+
assert(m_index < m_hashmap.m_capacity);
|
|
626
|
+
if (m_item->next)
|
|
627
|
+
m_item = m_item->next;
|
|
628
|
+
else {
|
|
629
|
+
do {
|
|
630
|
+
++m_index;
|
|
631
|
+
if (m_index >= m_hashmap.m_capacity) {
|
|
632
|
+
m_item = nullptr;
|
|
633
|
+
return *this; // reached the end
|
|
634
|
+
}
|
|
635
|
+
m_item = m_hashmap.m_map[m_index];
|
|
636
|
+
} while (!m_item);
|
|
637
|
+
}
|
|
638
|
+
return *this;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
iterator operator++(int) {
|
|
642
|
+
iterator i = *this;
|
|
643
|
+
++(*this);
|
|
644
|
+
return i;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
KeyT key() {
|
|
648
|
+
if (m_item)
|
|
649
|
+
return m_item->key;
|
|
650
|
+
return nullptr;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
T value() {
|
|
654
|
+
if (m_item)
|
|
655
|
+
return m_item->value;
|
|
656
|
+
return nullptr;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
std::pair<KeyT, T> operator*() {
|
|
660
|
+
return std::pair<KeyT, T> { m_item->key, m_item->value };
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
friend bool operator==(const iterator &i1, const iterator &i2) {
|
|
664
|
+
return i1.m_index == i2.m_index && i1.m_item == i2.m_item;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
friend bool operator!=(const iterator &i1, const iterator &i2) {
|
|
668
|
+
return i1.m_index != i2.m_index || i1.m_item != i2.m_item;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
Item *item() { return m_item; }
|
|
672
|
+
|
|
673
|
+
private:
|
|
674
|
+
Hashmap &m_hashmap;
|
|
675
|
+
size_t m_index { 0 };
|
|
676
|
+
Hashmap::Item *m_item { nullptr };
|
|
677
|
+
};
|
|
678
|
+
|
|
679
|
+
/**
|
|
680
|
+
* Returns an iterator over the Hashmap.
|
|
681
|
+
* The iterator is dereferenced to a std::pair,
|
|
682
|
+
* so you call .first and .second to get the key and
|
|
683
|
+
* value, respectively.
|
|
684
|
+
*
|
|
685
|
+
* ```
|
|
686
|
+
* auto map = Hashmap<String, Thing>(HashType::TMString);
|
|
687
|
+
* map.put("foo", Thing(1));
|
|
688
|
+
* for (std::pair item : map) {
|
|
689
|
+
* assert_str_eq("foo", item.first);
|
|
690
|
+
* assert_eq(Thing(1), item.second);
|
|
691
|
+
* }
|
|
692
|
+
* ```
|
|
693
|
+
*/
|
|
694
|
+
iterator begin() {
|
|
695
|
+
if (m_size == 0) return end();
|
|
696
|
+
assert(m_map);
|
|
697
|
+
Item *item = nullptr;
|
|
698
|
+
size_t index;
|
|
699
|
+
for (index = 0; index < m_capacity; ++index) {
|
|
700
|
+
item = m_map[index];
|
|
701
|
+
if (item)
|
|
702
|
+
break;
|
|
703
|
+
}
|
|
704
|
+
return iterator { *this, index, item };
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
iterator end() {
|
|
708
|
+
return iterator { *this, m_capacity, nullptr };
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
private:
|
|
712
|
+
// Returns an integer from 0-100
|
|
713
|
+
size_t load_factor() { return m_size * 100 / m_capacity; }
|
|
714
|
+
|
|
715
|
+
size_t index_for_hash(size_t hash) const {
|
|
716
|
+
// This is an optimization for hash % capacity that is only possible
|
|
717
|
+
// because capacity is always a power of two.
|
|
718
|
+
assert((m_capacity & (m_capacity - 1)) == 0);
|
|
719
|
+
return hash & (m_capacity - 1);
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
size_t calculate_map_size(size_t num_items) {
|
|
723
|
+
size_t target_size = std::max<size_t>(4, num_items) * 100 / HASHMAP_TARGET_LOAD_FACTOR + 1;
|
|
724
|
+
|
|
725
|
+
// Round up to the next power of two (if the value is not already a power of two)
|
|
726
|
+
// TODO: This can be replaced by std::bit_ceil in C++20
|
|
727
|
+
target_size--;
|
|
728
|
+
for (size_t i = 1; i < sizeof(size_t) * 8; i *= 2) {
|
|
729
|
+
target_size |= target_size >> i;
|
|
730
|
+
}
|
|
731
|
+
return ++target_size;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
void rehash() {
|
|
735
|
+
auto old_capacity = m_capacity;
|
|
736
|
+
m_capacity = calculate_map_size(m_size);
|
|
737
|
+
auto new_map = new Item *[m_capacity] {};
|
|
738
|
+
for (size_t i = 0; i < old_capacity; i++) {
|
|
739
|
+
auto item = m_map[i];
|
|
740
|
+
while (item) {
|
|
741
|
+
auto next_item = item->next;
|
|
742
|
+
auto new_index = index_for_hash(item->hash);
|
|
743
|
+
insert_item(new_map, new_index, item);
|
|
744
|
+
item = next_item;
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
auto old_map = m_map;
|
|
748
|
+
m_map = new_map;
|
|
749
|
+
delete[] old_map;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
void insert_item(Item **map, size_t index, Item *item) {
|
|
753
|
+
auto existing_item = map[index];
|
|
754
|
+
if (existing_item) {
|
|
755
|
+
while (existing_item && existing_item->next) {
|
|
756
|
+
existing_item = existing_item->next;
|
|
757
|
+
}
|
|
758
|
+
existing_item->next = item;
|
|
759
|
+
} else {
|
|
760
|
+
map[index] = item;
|
|
761
|
+
}
|
|
762
|
+
item->next = nullptr;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
void delete_item(size_t index, Item *item) {
|
|
766
|
+
m_map[index] = item->next;
|
|
767
|
+
free_key(item->key);
|
|
768
|
+
delete item;
|
|
769
|
+
m_size--;
|
|
770
|
+
if (load_factor() < HASHMAP_MIN_LOAD_FACTOR)
|
|
771
|
+
rehash();
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
void delete_item(Item *item_before, Item *item) {
|
|
775
|
+
item_before->next = item->next;
|
|
776
|
+
free_key(item->key);
|
|
777
|
+
delete item;
|
|
778
|
+
m_size--;
|
|
779
|
+
if (load_factor() < HASHMAP_MIN_LOAD_FACTOR)
|
|
780
|
+
rehash();
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
void copy_items_from(const Hashmap &other) {
|
|
784
|
+
if (!other.m_map) return;
|
|
785
|
+
for (size_t i = 0; i < m_capacity; i++) {
|
|
786
|
+
auto item = other.m_map[i];
|
|
787
|
+
if (item) {
|
|
788
|
+
auto my_item = new Item { *item };
|
|
789
|
+
my_item->key = duplicate_key(item->key);
|
|
790
|
+
m_map[i] = my_item;
|
|
791
|
+
m_size++;
|
|
792
|
+
while (item->next) {
|
|
793
|
+
item = item->next;
|
|
794
|
+
my_item->next = new Item { *item };
|
|
795
|
+
my_item->next->key = duplicate_key(item->key);
|
|
796
|
+
my_item = my_item->next;
|
|
797
|
+
m_size++;
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
KeyT duplicate_key(KeyT &key) {
|
|
804
|
+
if constexpr (std::is_same_v<char *, KeyT> || std::is_same_v<const char *, KeyT>) {
|
|
805
|
+
return strdup(key);
|
|
806
|
+
} else {
|
|
807
|
+
return key;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
void free_key(KeyT &key) {
|
|
812
|
+
if constexpr (std::is_same_v<char *, KeyT> || std::is_same_v<const char *, KeyT>) {
|
|
813
|
+
free(key);
|
|
814
|
+
} else {
|
|
815
|
+
(void)key; // don't warn/error about unused parameter
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
size_t m_size { 0 };
|
|
820
|
+
size_t m_capacity { 0 };
|
|
821
|
+
Item **m_map { nullptr };
|
|
822
|
+
|
|
823
|
+
HashFn *m_hash_fn { nullptr };
|
|
824
|
+
CompareFn *m_compare_fn { nullptr };
|
|
825
|
+
};
|
|
826
|
+
}
|