From aa41f26a26fcff212ecc55750a4806d83a6cf5dc Mon Sep 17 00:00:00 2001 From: Tom Willemse Date: Sat, 18 Sep 2021 11:10:11 -0700 Subject: [PATCH] Chapter 19.4 - 19.5 --- clox/src/CMakeLists.txt | 2 + clox/src/object.c | 30 ++++++++- clox/src/object.h | 1 + clox/src/table.c | 142 ++++++++++++++++++++++++++++++++++++++++ clox/src/table.h | 27 ++++++++ clox/src/value.c | 5 +- clox/src/vm.c | 6 +- clox/src/vm.h | 2 + 8 files changed, 207 insertions(+), 8 deletions(-) create mode 100644 clox/src/table.c create mode 100644 clox/src/table.h diff --git a/clox/src/CMakeLists.txt b/clox/src/CMakeLists.txt index 0deeb6a..edee2cc 100644 --- a/clox/src/CMakeLists.txt +++ b/clox/src/CMakeLists.txt @@ -16,6 +16,8 @@ add_executable(Lox scanner.c object.h object.c + table.h + table.c main.c) install(TARGETS Lox) diff --git a/clox/src/object.c b/clox/src/object.c index 9622327..361e315 100644 --- a/clox/src/object.c +++ b/clox/src/object.c @@ -3,6 +3,7 @@ #include "memory.h" #include "object.h" +#include "table.h" #include "value.h" #include "vm.h" @@ -18,22 +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 ^= (uint32_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) { diff --git a/clox/src/object.h b/clox/src/object.h index 965b905..717031b 100644 --- a/clox/src/object.h +++ b/clox/src/object.h @@ -24,6 +24,7 @@ struct ObjString { Obj obj; int length; char *chars; + uint32_t hash; }; ObjString *takeString(char *chars, int length); diff --git a/clox/src/table.c b/clox/src/table.c new file mode 100644 index 0000000..5b6f3f9 --- /dev/null +++ b/clox/src/table.c @@ -0,0 +1,142 @@ +#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 entry. + return entry; + } + + 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) { + 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 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/src/table.h b/clox/src/table.h new file mode 100644 index 0000000..b4c3271 --- /dev/null +++ b/clox/src/table.h @@ -0,0 +1,27 @@ +#ifndef TABLE_H +#define 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 *talbe, const char *chars, int length, + uint32_t hash); + +#endif diff --git a/clox/src/value.c b/clox/src/value.c index a5f8540..28d1c9f 100644 --- a/clox/src/value.c +++ b/clox/src/value.c @@ -56,10 +56,7 @@ bool valuesEqual(Value a, Value b) { 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; + return AS_OBJ(a) == AS_OBJ(b); } default: return false; /* Unreachable */ diff --git a/clox/src/vm.c b/clox/src/vm.c index d253d67..e35a1e0 100644 --- a/clox/src/vm.c +++ b/clox/src/vm.c @@ -29,9 +29,13 @@ static void runtimeError(const char *format, ...) { void initVM() { resetStack(); vm.objects = NULL; + initTable(&vm.strings); } -void freeVM() { freeObjects(); } +void freeVM() { + freeTable(&vm.strings); + freeObjects(); +} void push(Value value) { *vm.stackTop = value; diff --git a/clox/src/vm.h b/clox/src/vm.h index 6c6ada8..f11c1dd 100644 --- a/clox/src/vm.h +++ b/clox/src/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;