Skip to content

Commit

Permalink
@@@ wip
Browse files Browse the repository at this point in the history
  • Loading branch information
vegerot committed Nov 2, 2024
1 parent be22fb0 commit d54b277
Show file tree
Hide file tree
Showing 2 changed files with 173 additions and 41 deletions.
9 changes: 7 additions & 2 deletions bytecode-vm-compiler/src/object.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ static LoxObj* allocateObj(VM* vm, size_t size, ObjType type) {
}

// TODO: refactor to use `newEmptyLoxString`
LoxString* newLoxStringFromCString(VM* vm, char const* cString, size_t length,
uint32_t hash) {
LoxString* newLoxStringFromCStringAndHash(VM* vm, char const* cString,
size_t length, uint32_t hash) {
LoxString* str = (LoxString*)allocateObj(
vm, sizeof(LoxString) + (length + 1) * sizeof(char), OBJ_STRING);

Expand All @@ -56,6 +56,11 @@ LoxString* newEmptyLoxString(VM* vm, size_t length) {
return str;
}

LoxString* newLoxStringFromCString(VM* vm, char const* cString, size_t length) {
uint32_t hash = computeHashOfCString(cString, length);
return newLoxStringFromCStringAndHash(vm, cString, length, hash);
}

uint32_t computeHashOfCString(char const* chars, size_t length) {
uint32_t hash = 2166136261u;
for (size_t i = 0; i < length; ++i) {
Expand Down
205 changes: 166 additions & 39 deletions bytecode-vm-compiler/test/test-table.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,21 @@
#include "../src/vm.h"
#include <string.h>
LoxString* newLoxStringFromCString(VM* vm, char const* cString, size_t length);
LoxString* newLoxStringFromCStringAndHash(VM* vm, char const* cString,
size_t length, uint32_t hash);

#include "../src/memory.h"
#include "../src/value.h"

typedef enum { ENTRY_ERR, ENTRY_EMPTY, ENTRY_VALUE, ENTRY_TOMBSTONE } EntryType;
typedef struct {
EntryType type;
LoxString* key;
Value value;
} TableEntry;

typedef struct {
/** number of non-ENTRY_EMPTY entries */
size_t count;
size_t capacity;
TableEntry* entries;
Expand All @@ -31,12 +36,13 @@ static void adjustCapacity(LoxTable* this, size_t newCapacity) {

// init each entry
for (size_t i = 0; i < newCapacity; ++i) {
newEntries[i].key = NULL;
newEntries[i].value = NIL_VAL;
newEntries[i] =
(TableEntry){.type = ENTRY_EMPTY, .key = NULL, .value = NIL_VAL};
}
LoxTable newThis = {
.capacity = newCapacity,
.count = this->count,
/** set in `tableAddAll */
.count = 0,
.entries = newEntries,
};

Expand All @@ -46,8 +52,7 @@ static void adjustCapacity(LoxTable* this, size_t newCapacity) {
free(this->entries);
}

this->capacity = newThis.capacity;
this->entries = newThis.entries;
*this = newThis;
}

void tableInit(LoxTable* this, size_t initialCapacity) {
Expand All @@ -68,12 +73,19 @@ void tableFree(LoxTable* table) {
}

static TableEntry* findEntry(LoxTable const* this, LoxString const* key) {
if (this->capacity == 0)
LOX_ASSERT(false && "attempting to find entry in uninitialized table");
uint32_t index = key->hash % this->capacity;
TableEntry* lastTombstone = NULL;
while (true) {
// printf("index: %u\n", index);
TableEntry* curr = &this->entries[index];
if (curr->key == NULL || curr->key->hash == key->hash)
if (curr->type == ENTRY_VALUE && curr->key->hash == key->hash)
return curr;
if (curr->type == ENTRY_TOMBSTONE)
lastTombstone = curr;
if (curr->type == ENTRY_EMPTY)
return lastTombstone == NULL ? curr : lastTombstone;

index = (index + 1) % (this->capacity);
}
}
Expand All @@ -83,12 +95,16 @@ void tableAddAll(LoxTable* from, LoxTable* to) {
return;
for (size_t i = 0; i < from->capacity; ++i) {
TableEntry* entry = &from->entries[i];
if (entry->key != NULL) {
if (entry->type == ENTRY_VALUE) {
++to->count;
tableSet(to, entry->key, entry->value);
}
}
}

#define TABLE_MAX_LOAD_RATIO 0.69
static_assert((int)TABLE_MAX_LOAD_RATIO == 0,
"TABLE_MAX_LOAD_RATIO must be less than 1");
bool tableSet(LoxTable* this, LoxString* key, Value value) {
// FIXME: this conversion looks weird. Probably should multiply and divide
// whole numbers instead
Expand All @@ -98,30 +114,65 @@ bool tableSet(LoxTable* this, LoxString* key, Value value) {
adjustCapacity(this, newCapacity);
}
TableEntry* entry = findEntry(this, key);
++this->count;
if (entry->type == ENTRY_EMPTY)
++this->count;

entry->type = ENTRY_VALUE;
entry->key = key;
entry->value = value;
return true;
}
Value* tableGet(LoxTable* table, LoxString* key);
Value* tableGet(LoxTable* this, LoxString* key) {
return &findEntry(this, key)->value;
Value* tableGet(LoxTable const* table, LoxString* key);
Value* tableGet(LoxTable const* this, LoxString* key) {
if (this->count == 0)
return NULL;
TableEntry* entry = findEntry(this, key);
if (entry->type != ENTRY_VALUE) {
return NULL;
}
return &entry->value;
}

bool tableDelete(LoxTable* this, LoxString const* key);
bool tableDelete(LoxTable* this, LoxString const* key) {
if (this->count == 0)
return false;
TableEntry* entry = findEntry(this, key);
if (entry->type != ENTRY_VALUE)
return false;
*entry =
(TableEntry){.type = ENTRY_TOMBSTONE, .key = NULL, .value = NIL_VAL};
--this->count;
return true;
}

// -----------------------

static void basic(void) {
initVM();
VM* vm = getVM_();
LoxTable table;
tableInit(&table, 0);
char const* k = "key";
LoxString* key = newLoxStringFromCString(vm, "key", strlen(k));
Value want = NUMBER_VAL(42.0);
tableSet(&table, key, want);

Value* res = tableGet(&table, key);
LOX_ASSERT_EQUALS(table.count, 0);

char const* k1 = "key1";
LoxString* key1 = newLoxStringFromCString(vm, k1, strlen(k1));
Value one = NUMBER_VAL(1);
tableSet(&table, key1, one);

LOX_ASSERT_VALUE_EQUALS(*tableGet(&table, key1), one);
LOX_ASSERT_EQUALS(table.count, 1);

char const* k2 = "key2";
LoxString* key2 = newLoxStringFromCString(vm, k2, strlen(k2));
Value two = NUMBER_VAL(2);
tableSet(&table, key2, two);

LOX_ASSERT_VALUE_EQUALS(*tableGet(&table, key1), one);
LOX_ASSERT_VALUE_EQUALS(*tableGet(&table, key2), two);
LOX_ASSERT_EQUALS(table.count, 2);

LOX_ASSERT_VALUE_EQUALS(*res, want);
tableFree(&table);
freeVM();
}
Expand All @@ -139,12 +190,83 @@ static void overwrite(void) {
Value* res = tableGet(&table, key);

LOX_ASSERT_VALUE_EQUALS(*res, one);
LOX_ASSERT_EQUALS(table.count, 1);
Value want = NUMBER_VAL(2);
tableSet(&table, key, want);

res = tableGet(&table, key);

LOX_ASSERT_VALUE_EQUALS(*res, want);
LOX_ASSERT_EQUALS(table.count, 1);
tableFree(&table);
freeVM();
}

static void delete(void) {
initVM();
VM* vm = getVM_();
LoxTable table;
tableInit(&table, 8);
char const* k1 = "key1";
uint32_t hash1 = table.capacity * 1;
LoxString* key1 = newLoxStringFromCStringAndHash(vm, k1, strlen(k1), hash1);
Value one = NUMBER_VAL(1);
tableSet(&table, key1, one);

char const* k2 = "key2";
uint32_t hash2 = table.capacity * 2;
LoxString* key2 = newLoxStringFromCStringAndHash(vm, k2, strlen(k2), hash2);
Value two = NUMBER_VAL(2);
tableSet(&table, key2, two);

LOX_ASSERT_EQUALS(table.count, 2);
LOX_ASSERT_VALUE_EQUALS(*tableGet(&table, key1), one);
LOX_ASSERT_EQUALS(tableDelete(&table, key1), true)
LOX_ASSERT(tableGet(&table, key1) == NULL);
LOX_ASSERT_EQUALS(tableDelete(&table, key1), false)
LOX_ASSERT_EQUALS(table.count, 1);

LOX_ASSERT_VALUE_EQUALS(*tableGet(&table, key2), two);
LOX_ASSERT_EQUALS(tableDelete(&table, key2), true)
LOX_ASSERT(tableGet(&table, key2) == NULL);
LOX_ASSERT_EQUALS(tableDelete(&table, key2), false)
LOX_ASSERT_EQUALS(table.count, 0);

tableFree(&table);
freeVM();
}
static void deletenoinfiniteloop(void) {
initVM();
VM* vm = getVM_();
LoxTable table;
tableInit(&table, 2);
char const* k1 = "key1";
uint32_t hash1 = table.capacity * 1;
LoxString* key1 = newLoxStringFromCStringAndHash(vm, k1, strlen(k1), hash1);
Value one = NUMBER_VAL(1);
tableSet(&table, key1, one);

char const* k2 = "key2";
uint32_t hash2 = table.capacity * 2;
LoxString* key2 = newLoxStringFromCStringAndHash(vm, k2, strlen(k2), hash2);
Value two = NUMBER_VAL(2);
tableSet(&table, key2, two);

LOX_ASSERT_VALUE_EQUALS(*tableGet(&table, key1), one);
LOX_ASSERT_EQUALS(tableDelete(&table, key1), true)
LOX_ASSERT(tableGet(&table, key1) == NULL);
LOX_ASSERT_EQUALS(tableDelete(&table, key1), false)

LOX_ASSERT_VALUE_EQUALS(*tableGet(&table, key2), two);
LOX_ASSERT_EQUALS(tableDelete(&table, key2), true)
LOX_ASSERT(tableGet(&table, key1) == NULL);
LOX_ASSERT_EQUALS(tableDelete(&table, key2), false)

char const* k3 = "key3";
uint32_t hash3 = table.capacity * 3;
LoxString* key3 = newLoxStringFromCStringAndHash(vm, k3, strlen(k3), hash3);
findEntry(&table, key3);

tableFree(&table);
freeVM();
}
Expand All @@ -165,45 +287,50 @@ static void grow(void) {
LOX_ASSERT_VALUE_EQUALS(*res, one);
LOX_ASSERT_EQUALS(table.capacity, 8);

res = tableGet(&table, key);

tableFree(&table);
freeVM();
}

/**
* This test no longer does what it was designed for.
* When I wrote this test, the hash function was hardcoded to always return 69,
* and this test passed. Now we have a better hash function.
* This tests the Open Addressing behavior of the hash table, i.e. the behavior
* when two keys are mapped to the same offset in the table.
*
* Problem: I don't have a direct way of testing it.
* Testing strategy: fill the table with a bunch of keys, and _surely_ at some
* point there will be a collision
*/
static void collision(void) {
initVM();
VM* vm = getVM_();
LoxTable table;
tableInit(&table, 0);
char const* k1 = "costarring";
LoxString* key1 = newLoxStringFromCString(vm, k1, strlen(k1));
Value one = NUMBER_VAL(1);
tableSet(&table, key1, one);

char const* k2 = "liquid";
LoxString* key2 = newLoxStringFromCString(vm, k2, strlen(k2));
Value two = NUMBER_VAL(2);
tableSet(&table, key2, two);
// fill table with a bunch of keys. Assume there are some collisions...
for (char c = 'A'; c < (char)'z'; ++c) {
char const k[] = {c, '\0'};
LoxString* key = newLoxStringFromCString(vm, k, strlen(k));
Value val = NUMBER_VAL(c);
tableSet(&table, key, val);
}

Value* res1 = tableGet(&table, key1);
Value* res2 = tableGet(&table, key2);
// check everything is in the table
for (char c = 'A'; c < (char)'z'; ++c) {
char const k[] = {c, '\0'};
LoxString* key = newLoxStringFromCString(vm, k, strlen(k));
Value lol = NUMBER_VAL(c);
Value* have = tableGet(&table, key);
LOX_ASSERT_VALUE_EQUALS(*have, lol);
}

LOX_ASSERT_VALUE_EQUALS(*res1, one);
LOX_ASSERT_VALUE_EQUALS(*res2, two);
tableFree(&table);
freeVM();
}

int main(void) {
// basic();
// overwrite();
// delete();
basic();
overwrite();
delete ();
deletenoinfiniteloop();
grow();

// FIXME: how do I test collisions?
Expand All @@ -215,5 +342,5 @@ int main(void) {
//
// Currently, when I want to test this function, I change the
// `computeHashOfCString` function to always return 69. That works too...
// collision();
collision();
}

0 comments on commit d54b277

Please sign in to comment.