mirror of
https://github.com/krgamestudios/Toy.git
synced 2026-04-15 23:04:08 +10:00
Implemented and tested literal dictionary
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
#include "opcodes.h"
|
||||
|
||||
#include "literal_array.h"
|
||||
#include "literal_dictionary.h"
|
||||
|
||||
typedef void (*PrintFn)(const char*);
|
||||
|
||||
|
||||
@@ -70,3 +70,84 @@ char* copyString(char* original, int length) {
|
||||
buffer[length] = '\0';
|
||||
return buffer;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,3 +73,5 @@ Literal _toStringLiteral(char* cstr);
|
||||
|
||||
//utils
|
||||
char* copyString(char* original, int length);
|
||||
bool literalsAreEqual(Literal lhs, Literal rhs);
|
||||
int hashLiteral(Literal lit);
|
||||
@@ -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:
|
||||
|
||||
201
source/literal_dictionary.c
Normal file
201
source/literal_dictionary.c
Normal file
@@ -0,0 +1,201 @@
|
||||
#include "literal_dictionary.h"
|
||||
|
||||
#include "memory.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
//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
|
||||
}
|
||||
}
|
||||
|
||||
25
source/literal_dictionary.h
Normal file
25
source/literal_dictionary.h
Normal file
@@ -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);
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user