From eafabfcb005ddc2931d535f6df122487e8e35497 Mon Sep 17 00:00:00 2001 From: Adnan Ioricce Date: Sun, 17 Nov 2024 00:54:58 -0300 Subject: [PATCH] [Chapter20&Hash] - Adding the hashmaps --- clox/object.c | 33 +++++++++++-- clox/object.h | 3 +- clox/table.c | 130 ++++++++++++++++++++++++++++++++++++++++++++++++++ clox/table.h | 26 ++++++++++ clox/value.c | 8 +--- clox/vm.c | 2 + clox/vm.h | 2 + 7 files changed, 192 insertions(+), 12 deletions(-) create mode 100644 clox/table.c create mode 100644 clox/table.h diff --git a/clox/object.c b/clox/object.c index 811feb9..2c609d4 100644 --- a/clox/object.c +++ b/clox/object.c @@ -3,6 +3,7 @@ #include "memory.h" #include "object.h" +#include "table.h" #include "value.h" #include "vm.h" @@ -18,21 +19,45 @@ static Obj* allocateObject(size_t size, ObjType type) { return object; } - -static ObjString* allocateString(char* chars, int length) { +static ObjString* allocateString(char* chars, int length, + uint32_t hash) +{ ObjString* string = ALLOCATE_OBJ(ObjString, OBJ_STRING); string->length = length; string->chars = chars; + string->hash = hash; + tableSet(&vm.strings, string, NIL_VAL); return string; } +static uint32_t hashString(const char* key, int length) { + uint32_t hash = 2166136261u; + for (int i = 0; i < length; i++) { + hash ^= (uint8_t)key[i]; + hash *= 16777619; + } + return hash; +} ObjString* takeString(char* chars, int length) { - return allocateString(chars, length); + uint32_t hash = hashString(chars, length); + ObjString* interned = tableFindString(&vm.strings, chars, length, + hash); + if (interned != NULL) { + FREE_ARRAY(char, chars, length + 1); + return interned; + } + + return allocateString(chars, length, hash); } ObjString* copyString(const char* chars, int length) { + uint32_t hash = hashString(chars, length); + ObjString* interned = tableFindString(&vm.strings, chars, length, + hash); + if (interned != NULL) return interned; + char* heapChars = ALLOCATE(char, length + 1); memcpy(heapChars, chars, length); heapChars[length] = '\0'; - return allocateString(heapChars, length); + return allocateString(heapChars, length, hash); } void printObject(Value value) { switch (OBJ_TYPE(value)) { diff --git a/clox/object.h b/clox/object.h index 82e38ed..d2b58d7 100644 --- a/clox/object.h +++ b/clox/object.h @@ -21,7 +21,8 @@ struct Obj { struct ObjString { Obj obj; int length; - char* chars; + char* chars; + uint32_t hash; }; ObjString* takeString(char* chars, int length); ObjString* copyString(const char* chars, int length); diff --git a/clox/table.c b/clox/table.c new file mode 100644 index 0000000..4a986a0 --- /dev/null +++ b/clox/table.c @@ -0,0 +1,130 @@ +#include +#include + +#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) { + if (IS_NIL(entry->value)) { + // Empty entry. + return tombstone != NULL ? tombstone : entry; + } else { + // We found a tombstone. + if (tombstone == NULL) tombstone = entry; + } + } else if (entry->key == key) { + // We found the key. + return entry; + } + + index = (index + 1) % capacity; + } +} + +static void adjustCapacity(Table* table, int capacity) { + Entry* entries = ALLOCATE(Entry, capacity); + for (int i = 0; i < capacity; i++) { + entries[i].key = NULL; + entries[i].value = NIL_VAL; + } + 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++; + } + FREE_ARRAY(Entry, table->entries, table->capacity); + table->entries = entries; + table->capacity = 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; +} +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); + } + + 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; + + // Place a tombstone in the entry. + entry->key = NULL; + entry->value = BOOL_VAL(true); + return true; +} + + +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); + } + } +} + +ObjString* tableFindString(Table* table, const char* chars, + int length, uint32_t hash) { + if (table->count == 0) return NULL; + + uint32_t index = hash % table->capacity; + 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; + } +} diff --git a/clox/table.h b/clox/table.h new file mode 100644 index 0000000..63fe09d --- /dev/null +++ b/clox/table.h @@ -0,0 +1,26 @@ +#ifndef clox_table_h +#define clox_table_h + +#include "common.h" +#include "value.h" + +typedef struct { + ObjString* key; + Value value; +} Entry; + +typedef struct { + int count; + int capacity; + Entry* entries; +} Table; + +void initTable(Table* table); +void freeTable(Table* table); +bool tableGet(Table* table, ObjString* key, Value* value); +bool tableSet(Table* table, ObjString* key, Value value); +bool tableDelete(Table* table, ObjString* key); +void tableAddAll(Table* from, Table* to); +ObjString* tableFindString(Table* table, const char* chars, + int length, uint32_t hash); +#endif diff --git a/clox/value.c b/clox/value.c index e06e97d..c2a15ef 100644 --- a/clox/value.c +++ b/clox/value.c @@ -42,13 +42,7 @@ bool valuesEqual(Value a, Value b) { case VAL_BOOL: return AS_BOOL(a) == AS_BOOL(b); case VAL_NIL: return true; case VAL_NUMBER: return AS_NUMBER(a) == AS_NUMBER(b); - case VAL_OBJ: { - ObjString* aString = AS_STRING(a); - ObjString* bString = AS_STRING(b); - return aString->length == bString->length && - memcmp(aString->chars, bString->chars, - aString->length) == 0; - } + case VAL_OBJ: return AS_OBJ(a) == AS_OBJ(b); default: return false; // Unreachable. } } diff --git a/clox/vm.c b/clox/vm.c index 3a70840..6420f92 100644 --- a/clox/vm.c +++ b/clox/vm.c @@ -28,9 +28,11 @@ static void runtimeError(const char* format, ...) { void initVM() { resetStack(); vm.objects = NULL; + initTable(&vm.strings); } void freeVM() { + freeTable(&vm.strings); freeObjects(); } void push(Value value) { diff --git a/clox/vm.h b/clox/vm.h index c8b1d39..6cd3374 100644 --- a/clox/vm.h +++ b/clox/vm.h @@ -2,6 +2,7 @@ #define clox_vm_h #include "chunk.h" +#include "table.h" #include "value.h" #define STACK_MAX 256 @@ -11,6 +12,7 @@ typedef struct { uint8_t* ip; Value stack[STACK_MAX]; Value* stackTop; + Table strings; Obj* objects; } VM; typedef enum {