diff --git a/source/toy_array.c b/source/toy_array.c index 01ed16d..64c7f1f 100644 --- a/source/toy_array.c +++ b/source/toy_array.c @@ -14,7 +14,7 @@ Toy_Array* Toy_resizeArray(Toy_Array* paramArray, unsigned int capacity) { if (array == NULL) { fprintf(stderr, TOY_CC_ERROR "ERROR: Failed to allocate a 'Toy_Array' of %d capacity\n" TOY_CC_RESET, (int)capacity); - exit(1); + exit(-1); } array->capacity = capacity; diff --git a/source/toy_table.c b/source/toy_table.c new file mode 100644 index 0000000..5049112 --- /dev/null +++ b/source/toy_table.c @@ -0,0 +1,162 @@ +#include "toy_table.h" +#include "toy_console_colors.h" + +#include +#include +#include + +//'count' actually tracks the number of values +#define MIN_CAPACITY 16 + +//utils +static Toy_Table* adjustTableCapacity(Toy_Table* oldTable, unsigned int newCapacity) { + //allocate and zero a new table in memory + Toy_Table* newTable = malloc(newCapacity * sizeof(Toy_TableEntry) + sizeof(Toy_Table)); + + newTable->capacity = newCapacity; + newTable->count = 0; + newTable->minPsl = 0; + newTable->maxPsl = 0; + + //unlike other structures, the empty space in a table needs to be null + memset(newTable + 1, 0, newTable->capacity * sizeof(Toy_TableEntry)); + + if (oldTable == NULL) { //for initial allocations + return newTable; + } + + //for each entry in the old table, copy it into the new table + for (int i = 0; i < oldTable->capacity; i++) { + Toy_insertTable(&newTable, oldTable->data[i].key, oldTable->data[i].value); + } + + //clean up and return + free(oldTable); + return newTable; +} + +//exposed functions +Toy_Table* Toy_allocateTable() { + return adjustTableCapacity(NULL, MIN_CAPACITY); +} + +void Toy_freeTable(Toy_Table* table) { + //TODO: slip in a call to free the complex values here + + free(table); +} + +void Toy_insertTable(Toy_Table** table, Toy_Value key, Toy_Value value) { + if (TOY_VALUE_IS_NULL(key) || TOY_VALUE_IS_BOOLEAN(key)) { //TODO: disallow functions and opaques + fprintf(stderr, TOY_CC_ERROR "ERROR: Bad table key\n" TOY_CC_RESET); + exit(-1); //TODO: #127 + } + + //expand the capacity + if ((*table)->capacity < (*table)->count * (1 / 0.75f)) { + (*table) = adjustTableCapacity(*table, (*table)->capacity * 2); + } + + //insert + unsigned int probe = Toy_hashValue(key) % (*table)->capacity; + Toy_TableEntry entry = (Toy_TableEntry){ .key = key, .value = value, .psl = 0 }; + + while (true) { + //if this spot is free, insert and return + if (TOY_VALUE_IS_NULL((*table)->data[probe].key)) { + (*table)->data[probe] = entry; + + (*table)->count++; + + //TODO: benchmark the psl optimisation + (*table)->minPsl = entry.psl < (*table)->minPsl ? entry.psl : (*table)->minPsl; + (*table)->maxPsl = entry.psl > (*table)->maxPsl ? entry.psl : (*table)->maxPsl; + + return; + } + + //if the new entry is "poorer", insert it and shift the old one + if ((*table)->data[probe].psl < entry.psl) { + Toy_TableEntry tmp = (*table)->data[probe]; + (*table)->data[probe] = entry; + entry = tmp; + } + + //adjust and continue + probe = (probe + 1) % (*table)->capacity; + entry.psl++; + } +} + +Toy_Value Toy_lookupTableValue(Toy_Table** table, Toy_Value key) { + if (TOY_VALUE_IS_NULL(key) || TOY_VALUE_IS_BOOLEAN(key)) { //TODO: disallow functions and opaques + fprintf(stderr, TOY_CC_ERROR "ERROR: Bad table key\n" TOY_CC_RESET); + exit(-1); //TODO: #127 + } + + //lookup + unsigned int probe = Toy_hashValue(key) % (*table)->capacity; + unsigned int counter = 0; + + while (true) { + //found the entry + if (TOY_VALUE_IS_EQUAL((*table)->data[probe].key, key)) { + return (*table)->data[probe].value; + } + + //if the psl is too big, or empty slot + if ((*table)->data[probe].psl > counter || TOY_VALUE_IS_NULL((*table)->data[probe].key)) { + return TOY_VALUE_TO_NULL(); + } + + //adjust and continue + probe = (probe + 1) % (*table)->capacity; + counter++; + } +} + +void Toy_removeTableEntry(Toy_Table** table, Toy_Value key) { + if (TOY_VALUE_IS_NULL(key) || TOY_VALUE_IS_BOOLEAN(key)) { //TODO: disallow functions and opaques + fprintf(stderr, TOY_CC_ERROR "ERROR: Bad table key\n" TOY_CC_RESET); + exit(-1); //TODO: #127 + } + + //lookup + unsigned int probe = Toy_hashValue(key) % (*table)->capacity; + unsigned int counter = 0; + unsigned int wipe = probe; //wiped at the end + + while (true) { + //found the entry + if (TOY_VALUE_IS_EQUAL((*table)->data[probe].key, key)) { + break; + } + + //if the psl is too big, or empty slot + if ((*table)->data[probe].psl > counter || TOY_VALUE_IS_NULL((*table)->data[probe].key)) { + return; + } + + //adjust and continue + probe = (probe + 1) % (*table)->capacity; + counter++; + } + + //shift down the later entries (past the probing point) + for (unsigned int i = (*table)->minPsl; i < (*table)->maxPsl; i++) { + unsigned int p = (probe + i + 0) % (*table)->capacity; //prev + unsigned int u = (probe + i + 1) % (*table)->capacity; //current + + //if the psl is too big, or an empty slot, stop + if ((*table)->data[u].psl > (counter + i) || TOY_VALUE_IS_NULL((*table)->data[u].key)) { + break; + } + + (*table)->data[p] = (*table)->data[u]; + (*table)->data[p].psl--; + wipe = wipe % (*table)->capacity; + } + + //finally, wipe the removed entry + (*table)->data[wipe] = (Toy_TableEntry){ .key = TOY_VALUE_TO_NULL(), .value = TOY_VALUE_TO_NULL(), .psl = 0 }; +} \ No newline at end of file diff --git a/source/toy_table.h b/source/toy_table.h new file mode 100644 index 0000000..500718d --- /dev/null +++ b/source/toy_table.h @@ -0,0 +1,27 @@ +#pragma once + +#include "toy_common.h" + +#include "toy_value.h" + +//key-value entry, and probe sequence length - https://programming.guide/robin-hood-hashing.html +typedef struct Toy_TableEntry { //32 | 64 BITNESS + Toy_Value key; //8 | 8 + Toy_Value value; //8 | 8 + unsigned int psl; //4 | 4 +} Toy_TableEntry; //20 | 20 + +//key-value table (contains = count + tombstones) +typedef struct Toy_Table { //32 | 64 BITNESS + unsigned int capacity; //4 | 4 + unsigned int count; //4 | 4 + unsigned int minPsl; //4 | 4 + unsigned int maxPsl; //4 | 4 + Toy_TableEntry data[]; //- | - +} Toy_Table; //16 | 16 + +TOY_API Toy_Table* Toy_allocateTable(); +TOY_API void Toy_freeTable(Toy_Table* table); +TOY_API void Toy_insertTable(Toy_Table** table, Toy_Value key, Toy_Value value); +TOY_API Toy_Value Toy_lookupTableValue(Toy_Table** table, Toy_Value key); +TOY_API void Toy_removeTableEntry(Toy_Table** table, Toy_Value key); diff --git a/source/toy_value.c b/source/toy_value.c index d8fcae1..ba2d451 100644 --- a/source/toy_value.c +++ b/source/toy_value.c @@ -8,7 +8,7 @@ bool Toy_private_isTruthy(Toy_Value value) { //null is an error if (TOY_VALUE_IS_NULL(value)) { fprintf(stderr, TOY_CC_ERROR "ERROR: 'null' is neither true nor false\n" TOY_CC_RESET); - exit(-1); //TODO: return false or exit()? + exit(-1); //TODO: #127 } //only 'false' is falsy @@ -62,3 +62,48 @@ bool Toy_private_isEqual(Toy_Value left, Toy_Value right) { exit(-1); } } + +//hash utils +static unsigned int hashCString(const char* string) { + unsigned int hash = 2166136261u; + + for (unsigned int i = 0; string[i]; i++) { + hash *= string[i]; + hash ^= 16777619; + } + + return hash; +} + +static unsigned int hashUInt(unsigned int x) { + x = ((x >> 16) ^ x) * 0x45d9f3b; + x = ((x >> 16) ^ x) * 0x45d9f3b; + x = (x >> 16) ^ x; + return x; +} + + +unsigned int Toy_hashValue(Toy_Value value) { + switch(value.type) { + case TOY_VALUE_NULL: + return 0; + + case TOY_VALUE_BOOLEAN: + return TOY_VALUE_AS_BOOLEAN(value) ? 1 : 0; + + case TOY_VALUE_INTEGER: + return hashUInt(TOY_VALUE_AS_INTEGER(value)); + + case TOY_VALUE_FLOAT: + return hashUInt( *((int*)(&TOY_VALUE_AS_FLOAT(value))) ); + + case TOY_VALUE_STRING: + case TOY_VALUE_ARRAY: + case TOY_VALUE_DICTIONARY: + case TOY_VALUE_FUNCTION: + case TOY_VALUE_OPAQUE: + default: + fprintf(stderr, TOY_CC_ERROR "ERROR: Can't hash an unknown type %d\n" TOY_CC_RESET, value.type); + exit(-1); + } +} diff --git a/source/toy_value.h b/source/toy_value.h index ecd39b0..bc7cc8d 100644 --- a/source/toy_value.h +++ b/source/toy_value.h @@ -56,3 +56,6 @@ TOY_API bool Toy_private_isTruthy(Toy_Value value); #define TOY_VALUE_IS_EQUAL(left, right) Toy_private_isEqual(left, right) TOY_API bool Toy_private_isEqual(Toy_Value left, Toy_Value right); + +unsigned int Toy_hashValue(Toy_Value value); + diff --git a/tests/cases/test_table.c b/tests/cases/test_table.c new file mode 100644 index 0000000..3221196 --- /dev/null +++ b/tests/cases/test_table.c @@ -0,0 +1,27 @@ +#include "toy_table.h" +#include "toy_console_colors.h" + +#include + +int test_table_allocation() { + return 0; +} + +int main() { + //not finished yet + fprintf(stderr, TOY_CC_WARN "'Toy_Table' is not yet tested\n" TOY_CC_RESET); + return 0; + + //run each test set, returning the total errors given + int total = 0, res = 0; + + { + res = test_table_allocation(); + if (res == 0) { + printf(TOY_CC_NOTICE "All good\n" TOY_CC_RESET); + } + total += res; + } + + return total; +}