-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtable.c
148 lines (123 loc) · 4.17 KB
/
table.c
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
#include <stdlib.h>
#include <string.h>
#include "memory.h"
#include "object.h"
#include "table.h"
#include "value.h"
#define TABLE_MAX_LOAD 0.75
void initTable(Table* table) {
table->count = 0;
table->capacity = 0;
table->entries = NULL;
};
void freeTable(Table* table) {
FREE_ARRAY(Entry, table->entries, table->capacity);
initTable(table);
}
static Entry* findEntry(Entry* entries, int capacity, ObjString* key) {
uint32_t index = key->hash % capacity;
Entry* tombstone = NULL;
for (;;) {
Entry* entry = &entries[index];
if (entry->key == NULL) {
// we could either have an empty entry or a tombstone
if (IS_NIL(entry->value)) {
// here we have an empty entry so return early
// but reuse the first tombstone we saw if possible
return tombstone != NULL ? tombstone : entry;
} else {
// in this case, entry->value is true, so it's a tombstone
// and we keep probing
if (tombstone == NULL) tombstone = entry;
}
} else if (entry->key == key) {
// found actual bucket
return entry;
}
// linear probing: keep looking forward until
// key matches or we hit an empty bucket
index = (index + 1) % capacity;
}
}
bool tableGet(Table* table, ObjString* key, Value* value) {
if (table->count == 0) return false;
Entry* entry = findEntry(table->entries, table->capacity, key);
if (entry->key == NULL) return false;
*value = entry->value;
return true;
}
static void adjustCapacity(Table* table, int capacity) {
// prepare a new clean allocated table with future capacity
Entry* entries = ALLOCATE(Entry, capacity);
for (int i = 0; i < capacity; i++) {
entries[i].key = NULL;
entries[i].value = NIL_VAL;
}
// for present capacity, figure out the new table's buckets for current keys
table->count = 0;
for (int i = 0; i < table->capacity; i++) {
Entry* entry = &table->entries[i];
if (entry->key == NULL) continue;
Entry* dest = findEntry(entries, capacity, entry->key);
dest->key = entry->key;
dest->value = entry->value;
table->count++;
}
// release the old table
FREE_ARRAY(Entry, table->entries, table->capacity);
table->entries = entries;
table->capacity = capacity;
}
// sets key/value in the table; returns true if new value is written
bool tableSet(Table* table, ObjString* key, Value value) {
if (table->count + 1 > table->capacity * TABLE_MAX_LOAD) {
int capacity = GROW_CAPACITY(table->capacity);
adjustCapacity(table, capacity);
}
// findEntry either finds the actual key's bucketj or the first empty bucket
Entry* entry = findEntry(table->entries, table->capacity, key);
bool isNewKey = entry->key == NULL;
if (isNewKey && IS_NIL(entry->value)) table->count++;
entry->key = key;
entry->value = value;
return isNewKey;
}
bool tableDelete(Table *table, ObjString *key) {
if (table->count == 0) return false;
// find the entry
Entry* entry = findEntry(table->entries, table->capacity, key);
if (entry->key == NULL) return false;
// if we found an entry, add a tombstone. here it's a null key with true value
// since that combination won't be confused with an actual key/value
entry->key = NULL;
entry->value = BOOL_VAL(true);
return true;
};
// it's kind of like a ruby Hash.merge
void tableAddAll(Table* from, Table* to) {
for (int i = 0; i < from->capacity; i++) {
Entry* entry = &from->entries[i];
if (entry->key != NULL) {
tableSet(to, entry->key, entry->value);
}
}
}
// find a string in the table
ObjString* tableFindString(Table* table, const char* chars, int length, uint32_t hash) {
if (table->count == 0) return NULL;
uint32_t index = hash % table->capacity;
// this looks like findEntry but we memcmp chars instead of matching entry->key to key
for (;;) {
Entry* entry = &table->entries[index];
if (entry->key == NULL) {
// Stop if we find an empty non-tombstone entry.
if (IS_NIL(entry->value)) return NULL;
} else if (entry->key->length == length &&
entry->key->hash == hash &&
memcmp(entry->key->chars, chars, length) == 0) {
// We found it.
return entry->key;
}
index = (index + 1) % table->capacity;
}
}