From 6f4bfc0e10d996debdf163e4ce14ee131576763b Mon Sep 17 00:00:00 2001 From: Kayne Ruse Date: Mon, 8 Aug 2022 07:58:30 +0100 Subject: [PATCH] Implemented and tested literal dictionary --- source/interpreter.h | 1 + source/literal.c | 83 ++++++++++++++- source/literal.h | 4 +- source/literal_array.c | 1 - source/literal_dictionary.c | 201 ++++++++++++++++++++++++++++++++++++ source/literal_dictionary.h | 25 +++++ source/makefile | 8 +- source/repl_main.c | 27 +++++ 8 files changed, 343 insertions(+), 7 deletions(-) create mode 100644 source/literal_dictionary.c create mode 100644 source/literal_dictionary.h diff --git a/source/interpreter.h b/source/interpreter.h index 4903ad8..4e887c7 100644 --- a/source/interpreter.h +++ b/source/interpreter.h @@ -3,6 +3,7 @@ #include "opcodes.h" #include "literal_array.h" +#include "literal_dictionary.h" typedef void (*PrintFn)(const char*); diff --git a/source/literal.c b/source/literal.c index 91ed2d0..d19a511 100644 --- a/source/literal.c +++ b/source/literal.c @@ -69,4 +69,85 @@ char* copyString(char* original, int length) { strncpy(buffer, original, length); buffer[length] = '\0'; return buffer; -} \ No newline at end of file +} + +bool literalsAreEqual(Literal lhs, Literal rhs) { + if (lhs.type != rhs.type) { + // ints and floats are compatible + if ((IS_INTEGER(lhs) || IS_FLOAT(lhs)) && (IS_INTEGER(rhs) || IS_FLOAT(rhs))) { + if (IS_INTEGER(lhs)) { + return AS_INTEGER(lhs) + AS_FLOAT(rhs); + } + else { + return AS_FLOAT(lhs) + AS_INTEGER(rhs); + } + } + + return false; + } + + switch(lhs.type) { + case LITERAL_BOOLEAN: + return AS_BOOLEAN(lhs) == AS_BOOLEAN(rhs); + + case LITERAL_INTEGER: + return AS_INTEGER(lhs) == AS_INTEGER(rhs); + + case LITERAL_FLOAT: + return AS_FLOAT(lhs) == AS_FLOAT(rhs); + + case LITERAL_STRING: + if (STRLEN(lhs) != STRLEN(rhs)) { + return false; + } + return !strncmp(AS_STRING(lhs), AS_STRING(rhs), STRLEN(lhs)); + + default: + //should never bee seen + fprintf(stderr, "[Internal] Unrecognized literal type: %d", lhs.type); + return false; + } +} + +//hash functions +static unsigned int hashString(const char* string, int length) { + unsigned int hash = 2166136261u; + + for (int i = 0; i < length; i++) { + hash *= string[i]; + hash *= 16777619; + } + + return hash; +} + +static unsigned int hash(unsigned int x) { + x = ((x >> 16) ^ x) * 0x45d9f3b; + x = ((x >> 16) ^ x) * 0x45d9f3b; + x = (x >> 16) ^ x; + return x; +} + +int hashLiteral(Literal lit) { + switch(lit.type) { + case LITERAL_NULL: + return 0; + + case LITERAL_BOOLEAN: + return AS_BOOLEAN(lit) ? 1 : 0; + + case LITERAL_INTEGER: + return hash((unsigned int)AS_INTEGER(lit)); + + case LITERAL_FLOAT: + return hash( *(unsigned int*)(&AS_FLOAT(lit)) ); + + case LITERAL_STRING: + return hashString(AS_STRING(lit), STRLEN(lit)); + + default: + //should never bee seen + fprintf(stderr, "[Internal] Unrecognized literal type in hash: %d", lit.type); + return 0; + } +} diff --git a/source/literal.h b/source/literal.h index 7d6be6c..15ae44a 100644 --- a/source/literal.h +++ b/source/literal.h @@ -72,4 +72,6 @@ bool _isTruthy(Literal x); Literal _toStringLiteral(char* cstr); //utils -char* copyString(char* original, int length); \ No newline at end of file +char* copyString(char* original, int length); +bool literalsAreEqual(Literal lhs, Literal rhs); +int hashLiteral(Literal lit); \ No newline at end of file diff --git a/source/literal_array.c b/source/literal_array.c index e77308e..1ef1aef 100644 --- a/source/literal_array.c +++ b/source/literal_array.c @@ -85,7 +85,6 @@ int findLiteralIndex(LiteralArray* array, Literal literal) { if (strncmp(AS_STRING(array->literals[i]), AS_STRING(literal), STRLEN(literal)) == 0) { return i; } - continue; default: diff --git a/source/literal_dictionary.c b/source/literal_dictionary.c new file mode 100644 index 0000000..ef9873a --- /dev/null +++ b/source/literal_dictionary.c @@ -0,0 +1,201 @@ +#include "literal_dictionary.h" + +#include "memory.h" + +#include + +//util functions +static void setEntryValues(_entry* entry, Literal key, Literal value) { + //free the original string and overwrite it + if (IS_STRING(entry->key)) { + freeLiteral(entry->key); + } + + //take ownership of the copied string + if (IS_STRING(key)) { + char* buffer = ALLOCATE(char, STRLEN(key) + 1); + strncpy(buffer, AS_STRING(key), STRLEN(key)); + buffer[STRLEN(key)] = '\0'; + entry->key = TO_STRING_LITERAL(buffer); //buffer becomes a part of the key literal + } + + else { + entry->key = key; + } + + //values + freeLiteral(entry->value); + + //take ownership of the copied string + if (IS_STRING(value)) { + char* buffer = ALLOCATE(char, STRLEN(value) + 1); + strncpy(buffer, AS_STRING(value), STRLEN(value)); + buffer[STRLEN(value)] = '\0'; + entry->value = TO_STRING_LITERAL(buffer); + } + + else { + entry->value = value; + } +} + +static _entry* getEntryArray(_entry* array, int capacity, Literal key, unsigned int hash, bool mustExist) { + //find "key", starting at index + unsigned int index = hash % capacity; + unsigned int start = index; + + //increment once, so it can't equal start + index = (index + 1) % capacity; + + //literal probing and collision checking + while (index != start) { //WARNING: this is the only function allowed to retrieve an entry from the array + _entry* entry = &array[index]; + + if (IS_NULL(entry->key)) { //if key is empty, it's either empty or tombstone + if (IS_NULL(entry->value) && !mustExist) { + //found a truly empty bucket + return entry; + } + //else it's a tombstone - ignore + } else { + if (literalsAreEqual(key, entry->key)) { + return entry; + } + } + + index = (index + 1) % capacity; + } + + return NULL; +} + +static void adjustEntryCapacity(_entry** dictionaryHandle, int oldCapacity, int capacity) { + //new entry space + _entry* newEntries = ALLOCATE(_entry, capacity); + + for (int i = 0; i < capacity; i++) { + newEntries[i].key = TO_NULL_LITERAL; + newEntries[i].value = TO_NULL_LITERAL; + } + + //move the old array into the new one + for (int i = 0; i < oldCapacity; i++) { + if (IS_NULL((*dictionaryHandle)[i].key)) { + continue; + } + + //place the key and value in the new array (reusing string memory) + _entry* entry = getEntryArray(newEntries, capacity, TO_NULL_LITERAL, hashLiteral((*dictionaryHandle)[i].key), false); + + entry->key = (*dictionaryHandle)[i].key; + entry->value = (*dictionaryHandle)[i].value; + } + + //clear the old array + FREE_ARRAY(_entry, *dictionaryHandle, oldCapacity); + + *dictionaryHandle = newEntries; +} + +static bool setEntryArray(_entry** dictionaryHandle, int* capacityPtr, int count, Literal key, Literal value, int hash) { + //expand array if needed + if (count + 1 > *capacityPtr * DICTIONARY_MAX_LOAD) { + int oldCapacity = *capacityPtr; + *capacityPtr = GROW_CAPACITY(*capacityPtr); + adjustEntryCapacity(dictionaryHandle, oldCapacity, *capacityPtr); //custom rather than automatic reallocation + } + + _entry* entry = getEntryArray(*dictionaryHandle, *capacityPtr, key, hash, false); + + //true = count increase + if (IS_NULL(entry->key)) { + setEntryValues(entry, key, value); + return true; + } + else { + setEntryValues(entry, key, value); + return false; + } + + return false; +} + +static void freeEntry(_entry* entry) { + freeLiteral(entry->key); + freeLiteral(entry->value); + entry->key = TO_NULL_LITERAL; + entry->value = TO_NULL_LITERAL; +} + +static void freeEntryArray(_entry* array, int capacity) { + if (array == NULL) { + return; + } + + for (int i = 0; i < capacity; i++) { + if (!IS_NULL(array[i].key)) { + freeEntry(&array[i]); + } + } + + FREE_ARRAY(_entry, array, capacity); +} + +//exposed functions +void initLiteralDictionary(LiteralDictionary* dictionary) { + //HACK: because modulo by 0 is undefined, set the capacity to a non-zero value (and allocate the arrays) + dictionary->entries = NULL; + dictionary->capacity = GROW_CAPACITY(0); + dictionary->count = 0; + adjustEntryCapacity(&dictionary->entries, 0, dictionary->capacity); +} + +void freeLiteralDictionary(LiteralDictionary* dictionary) { + freeEntryArray(dictionary->entries, dictionary->capacity); + dictionary->capacity = 0; + dictionary->count = 0; +} + +void setLiteralDictionary(LiteralDictionary* dictionary, Literal key, Literal value) { + if (IS_NULL(key)) { + fprintf(stderr, "[Internal] Dictionaries can't have null keys\n"); + return; + } + + const int increment = setEntryArray(&dictionary->entries, &dictionary->capacity, dictionary->count, key, value, hashLiteral(key)); + + if (increment) { + dictionary->count++; + } +} + +Literal getLiteralDictionary(LiteralDictionary* dictionary, Literal key) { + if (IS_NULL(key)) { + fprintf(stderr, "[Internal] Dictionaries can't have null keys\n"); + return TO_NULL_LITERAL; + } + + _entry* entry = getEntryArray(dictionary->entries, dictionary->capacity, key, hashLiteral(key), true); + + if (entry != NULL) { + return entry->value; + } + else { + return TO_NULL_LITERAL; + } +} + +void removeLiteralDictionary(LiteralDictionary* dictionary, Literal key) { + if (IS_NULL(key)) { + fprintf(stderr, "[Internal] Dictionaries can't have null keys\n"); + return; + } + + _entry* entry = getEntryArray(dictionary->entries, dictionary->capacity, key, hashLiteral(key), true); + + if (entry != NULL) { + freeEntry(entry); + entry->value = TO_BOOLEAN_LITERAL(true); //tombstone + } +} + diff --git a/source/literal_dictionary.h b/source/literal_dictionary.h new file mode 100644 index 0000000..629ff64 --- /dev/null +++ b/source/literal_dictionary.h @@ -0,0 +1,25 @@ +#pragma once + +#include "common.h" +#include "literal.h" + +//TODO: benchmark this +#define DICTIONARY_MAX_LOAD 0.75 + +typedef struct _entry { + Literal key; + Literal value; +} _entry; + +typedef struct LiteralDictionary { + _entry* entries; + int capacity; + int count; +} LiteralDictionary; + +void initLiteralDictionary(LiteralDictionary* dictionary); +void freeLiteralDictionary(LiteralDictionary* dictionary); + +void setLiteralDictionary(LiteralDictionary* dictionary, Literal key, Literal value); +Literal getLiteralDictionary(LiteralDictionary* dictionary, Literal key); +void removeLiteralDictionary(LiteralDictionary* dictionary, Literal key); diff --git a/source/makefile b/source/makefile index 1c78cf0..4c8e881 100644 --- a/source/makefile +++ b/source/makefile @@ -1,10 +1,10 @@ CC=gcc -IDIR =. -CFLAGS=$(addprefix -I,$(IDIR)) -g # -Wall -W -pedantic -Wno-unused-parameter -Wno-unused-function -Wno-unused-variable -LIBS= +IDIR +=. +CFLAGS +=$(addprefix -I,$(IDIR)) -g -Wall -W -pedantic -Wno-unused-parameter -Wno-unused-function -Wno-unused-variable +LIBS += -ODIR=obj +ODIR = obj SRC = $(wildcard *.c) OBJ = $(addprefix $(ODIR)/,$(SRC:.c=.o)) diff --git a/source/repl_main.c b/source/repl_main.c index 5140bb4..135dd03 100644 --- a/source/repl_main.c +++ b/source/repl_main.c @@ -154,6 +154,32 @@ void repl() { freeInterpreter(&interpreter); } +void debug() { + LiteralDictionary dictionary; + + initLiteralDictionary(&dictionary); + + for (int i = 0; i < 100; i++) { + setLiteralDictionary(&dictionary, TO_INTEGER_LITERAL(i), TO_INTEGER_LITERAL(i * 2)); + } + + for (int i = 0; i < 100; i++) { + printf("%d: ", i); + printLiteral( getLiteralDictionary(&dictionary, TO_INTEGER_LITERAL(i)) ); + printf("\n"); + } + + printf("-------------"); + + for (int i = 0; i < dictionary.capacity; i++) { + printf("%d: ", i); + printLiteral(dictionary.entries[i].key); + printf("\n"); + } + + freeLiteralDictionary(&dictionary); +} + //entry point int main(int argc, const char* argv[]) { initCommand(argc, argv); @@ -189,6 +215,7 @@ int main(int argc, const char* argv[]) { return 0; } + // debug(); repl(); return 0;