diff --git a/source/toy_scope.c.txt b/source/toy_scope.c similarity index 62% rename from source/toy_scope.c.txt rename to source/toy_scope.c index dc105a7..d8bd7ca 100644 --- a/source/toy_scope.c.txt +++ b/source/toy_scope.c @@ -20,7 +20,7 @@ static void decrementRefCount(Toy_Scope* scope) { } } -static Toy_Value* lookupScope(Toy_Scope* scope, Toy_Value key, unsigned int hash, bool recursive) { +static Toy_Value* lookupScope(Toy_Scope* scope, Toy_String* key, unsigned int hash, bool recursive) { //terminate if (scope == NULL) { return NULL; @@ -31,11 +31,11 @@ static Toy_Value* lookupScope(Toy_Scope* scope, Toy_Value key, unsigned int hash while (true) { //found the entry - if (TOY_VALUES_ARE_EQUAL(scope->table->data[probe].key, key)) { + if (Toy_compareStrings(TOY_VALUE_AS_STRING(scope->table->data[probe].key), key)) { return &(scope->table->data[probe].value); } - //if its an empty slot + //if its an empty slot (didn't find it here) if (TOY_VALUE_IS_NULL(scope->table->data[probe].key)) { return recursive ? lookupScope(scope->next, key, hash, recursive) : NULL; } @@ -69,8 +69,15 @@ Toy_Scope* Toy_popScope(Toy_Scope* scope) { return scope->next; } -Toy_Scope* deepCopyScope(Toy_Bucket** bucketHandle, Toy_Scope* scope) { - Toy_Scope* newScope = Toy_pushScope(bucketHandle, scope->next); +Toy_Scope* Toy_deepCopyScope(Toy_Bucket** bucketHandle, Toy_Scope* scope) { + //copy/pasted from pushScope, so I can allocate the table manually + Toy_Scope* newScope = Toy_partitionBucket(bucketHandle, sizeof(Toy_Scope)); + + newScope->next = scope->next; + newScope->table = Toy_private_adjustTableCapacity(NULL, scope->table->capacity); + newScope->refCount = 0; + + incrementRefCount(newScope); //forcibly copy the contents for (int i = 0; i < scope->table->capacity; i++) { @@ -82,36 +89,36 @@ Toy_Scope* deepCopyScope(Toy_Bucket** bucketHandle, Toy_Scope* scope) { return newScope; } -void Toy_declareScope(Toy_Bucket** bucketHandle, Toy_Scope* scope, Toy_String key, Toy_Value value) { - if (key.type != TOY_STRING_NAME) { +void Toy_declareScope(Toy_Bucket** bucketHandle, Toy_Scope* scope, Toy_String* key, Toy_Value value) { + if (key->type != TOY_STRING_NAME) { fprintf(stderr, TOY_CC_ERROR "ERROR: Toy_Scope only allows name strings as keys\n" TOY_CC_RESET); exit(-1); } - Toy_Value* valuePtr = lookupScope(scope, key, Toy_hashValue(key), false); + Toy_Value* valuePtr = lookupScope(scope, key, Toy_hashString(key), false); if (valuePtr != NULL) { - char buffer[key.length + 256]; - snprintf(buffer, "Can't redefine a variable: %s", key.as.name.data); + char buffer[key->length + 256]; + sprintf(buffer, "Can't redefine a variable: %s", key->as.name.data); Toy_error(buffer); return; } - Toy_insertTable(&scope->table, key, value); + Toy_insertTable(&scope->table, TOY_VALUE_FROM_STRING(Toy_copyString(bucketHandle, key)), value); } -void Toy_assignScope(Toy_Bucket** bucketHandle, Toy_Scope* scope, Toy_String key, Toy_Value value) { - if (key.type != TOY_STRING_NAME) { +void Toy_assignScope(Toy_Bucket** bucketHandle, Toy_Scope* scope, Toy_String* key, Toy_Value value) { + if (key->type != TOY_STRING_NAME) { fprintf(stderr, TOY_CC_ERROR "ERROR: Toy_Scope only allows name strings as keys\n" TOY_CC_RESET); exit(-1); } - Toy_Value* valuePtr = lookupScope(scope, key, Toy_hashValue(key), true); + Toy_Value* valuePtr = lookupScope(scope, key, Toy_hashString(key), true); if (valuePtr == NULL) { - char buffer[key.length + 256]; - snprintf(buffer, "Undefined variable: %s", key.as.name.data); + char buffer[key->length + 256]; + sprintf(buffer, "Undefined variable: %s", key->as.name.data); Toy_error(buffer); return; } @@ -119,31 +126,31 @@ void Toy_assignScope(Toy_Bucket** bucketHandle, Toy_Scope* scope, Toy_String key *valuePtr = value; } -Toy_Value Toy_accessScope(Toy_Bucket** bucketHandle, Toy_Scope* scope, Toy_String key) { - if (key.type != TOY_STRING_NAME) { +Toy_Value Toy_accessScope(Toy_Bucket** bucketHandle, Toy_Scope* scope, Toy_String* key) { + if (key->type != TOY_STRING_NAME) { fprintf(stderr, TOY_CC_ERROR "ERROR: Toy_Scope only allows name strings as keys\n" TOY_CC_RESET); exit(-1); } - Toy_Value* valuePtr = lookupScope(scope, key, Toy_hashValue(key), true); + Toy_Value* valuePtr = lookupScope(scope, key, Toy_hashString(key), true); if (valuePtr == NULL) { - char buffer[key.length + 256]; - snprintf(buffer, "Undefined variable: %s", key.as.name.data); + char buffer[key->length + 256]; + sprintf(buffer, "Undefined variable: %s", key->as.name.data); Toy_error(buffer); - return; + return TOY_VALUE_FROM_NULL(); } return *valuePtr; } -bool Toy_isDeclaredScope(Toy_Bucket** bucketHandle, Toy_Scope* scope, Toy_String key) { - if (key.type != TOY_STRING_NAME) { +bool Toy_isDeclaredScope(Toy_Bucket** bucketHandle, Toy_Scope* scope, Toy_String* key) { + if (key->type != TOY_STRING_NAME) { fprintf(stderr, TOY_CC_ERROR "ERROR: Toy_Scope only allows name strings as keys\n" TOY_CC_RESET); exit(-1); } - Toy_Value* valuePtr = lookupScope(scope, key, Toy_hashValue(key), true); + Toy_Value* valuePtr = lookupScope(scope, key, Toy_hashString(key), true); return valuePtr != NULL; } diff --git a/source/toy_scope.h.txt b/source/toy_scope.h similarity index 70% rename from source/toy_scope.h.txt rename to source/toy_scope.h index 31f251b..b7395d9 100644 --- a/source/toy_scope.h.txt +++ b/source/toy_scope.h @@ -14,15 +14,15 @@ typedef struct Toy_Scope { unsigned int refCount; } Toy_Scope; -//handle deep scopes +//handle deep scopes - the scope is stored in the bucket, not the table TOY_API Toy_Scope* Toy_pushScope(Toy_Bucket** bucketHandle, Toy_Scope* scope); TOY_API Toy_Scope* Toy_popScope(Toy_Scope* scope); -TOY_API Toy_Scope* deepCopyScope(Toy_Bucket** bucketHandle, Toy_Scope* scope); +TOY_API Toy_Scope* Toy_deepCopyScope(Toy_Bucket** bucketHandle, Toy_Scope* scope); //manage the contents -TOY_API void Toy_declareScope(Toy_Bucket** bucketHandle, Toy_Scope* scope, Toy_String key, Toy_Value value); -TOY_API void Toy_assignScope(Toy_Bucket** bucketHandle, Toy_Scope* scope, Toy_String key, Toy_Value value); -TOY_API Toy_Value Toy_accessScope(Toy_Bucket** bucketHandle, Toy_Scope* scope, Toy_String key); +TOY_API void Toy_declareScope(Toy_Bucket** bucketHandle, Toy_Scope* scope, Toy_String* key, Toy_Value value); +TOY_API void Toy_assignScope(Toy_Bucket** bucketHandle, Toy_Scope* scope, Toy_String* key, Toy_Value value); +TOY_API Toy_Value Toy_accessScope(Toy_Bucket** bucketHandle, Toy_Scope* scope, Toy_String* key); -TOY_API bool Toy_isDeclaredScope(Toy_Bucket** bucketHandle, Toy_Scope* scope, Toy_String key); +TOY_API bool Toy_isDeclaredScope(Toy_Bucket** bucketHandle, Toy_Scope* scope, Toy_String* key); diff --git a/source/toy_table.c b/source/toy_table.c index 710b6b4..d3c14fb 100644 --- a/source/toy_table.c +++ b/source/toy_table.c @@ -54,7 +54,8 @@ static void probeAndInsert(Toy_Table** tableHandle, Toy_Value key, Toy_Value val } } -static Toy_Table* adjustTableCapacity(Toy_Table* oldTable, unsigned int newCapacity) { +//exposed functions +Toy_Table* Toy_private_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)); @@ -86,9 +87,8 @@ static Toy_Table* adjustTableCapacity(Toy_Table* oldTable, unsigned int newCapac return newTable; } -//exposed functions Toy_Table* Toy_allocateTable() { - return adjustTableCapacity(NULL, MIN_CAPACITY); + return Toy_private_adjustTableCapacity(NULL, MIN_CAPACITY); } void Toy_freeTable(Toy_Table* table) { @@ -104,7 +104,7 @@ void Toy_insertTable(Toy_Table** tableHandle, Toy_Value key, Toy_Value value) { //expand the capacity if ((*tableHandle)->count > (*tableHandle)->capacity * 0.8) { - (*tableHandle) = adjustTableCapacity((*tableHandle), (*tableHandle)->capacity * 2); + (*tableHandle) = Toy_private_adjustTableCapacity((*tableHandle), (*tableHandle)->capacity * 2); } probeAndInsert(tableHandle, key, value); diff --git a/source/toy_table.h b/source/toy_table.h index 640e5c0..1459ea4 100644 --- a/source/toy_table.h +++ b/source/toy_table.h @@ -26,3 +26,5 @@ TOY_API void Toy_insertTable(Toy_Table** tableHandle, Toy_Value key, Toy_Value v TOY_API Toy_Value Toy_lookupTable(Toy_Table** tableHandle, Toy_Value key); TOY_API void Toy_removeTable(Toy_Table** tableHandle, Toy_Value key); +//NOTE: exposed to skip unnecessary allocations within Toy_Scope +TOY_API Toy_Table* Toy_private_adjustTableCapacity(Toy_Table* oldTable, unsigned int newCapacity); diff --git a/tests/cases/test_scope.c b/tests/cases/test_scope.c new file mode 100644 index 0000000..1b1d11c --- /dev/null +++ b/tests/cases/test_scope.c @@ -0,0 +1,327 @@ +#include "toy_scope.h" +#include "toy_console_colors.h" + +#include "toy_bucket.h" + +#include +#include +#include + + + +int test_scope_allocation() { + //allocate and free a scope + { + //setup + Toy_Bucket* bucket = Toy_allocateBucket(TOY_BUCKET_IDEAL); + + Toy_Scope* scope = Toy_pushScope(&bucket, NULL); + + //check + if (scope == NULL || + scope->next != NULL || + scope->table == NULL || + scope->table->capacity != 16 || + scope->refCount != 1 || + + false) + { + fprintf(stderr, TOY_CC_ERROR "ERROR: Failed to allocate a Toy_Scope\n" TOY_CC_RESET); + Toy_popScope(scope); + Toy_freeBucket(&bucket); + return -1; + } + + //cleanup + Toy_popScope(scope); + Toy_freeBucket(&bucket); + } + + //allocate and free a list of scopes + { + //setup + Toy_Bucket* bucket = Toy_allocateBucket(TOY_BUCKET_IDEAL); + + //run + Toy_Scope* scope = NULL; + + for (int i = 0; i < 5; i++) { + scope = Toy_pushScope(&bucket, scope); + } + + //check + if ( + scope == NULL || + scope->next == NULL || + scope->table == NULL || + scope->table->capacity != 16 || + scope->refCount != 1 || + + scope->next->next == NULL || + scope->next->table == NULL || + scope->next->table->capacity != 16 || + scope->next->refCount != 2 || + + scope->next->next->next == NULL || + scope->next->next->table == NULL || + scope->next->next->table->capacity != 16 || + scope->next->next->refCount != 3 || + + scope->next->next->next->next == NULL || + scope->next->next->next->table == NULL || + scope->next->next->next->table->capacity != 16 || + scope->next->next->next->refCount != 4 || + + scope->next->next->next->next->next != NULL || + scope->next->next->next->next->table == NULL || + scope->next->next->next->next->table->capacity != 16 || + scope->next->next->next->next->refCount != 5 || //refCount includes all ancestors + + false) + { + fprintf(stderr, TOY_CC_ERROR "ERROR: Failed to allocate a list of Toy_Scope\n" TOY_CC_RESET); + while (scope) { + scope = Toy_popScope(scope); + } + Toy_freeBucket(&bucket); + return -1; + } + + //cleanup + while (scope) { + scope = Toy_popScope(scope); + } + Toy_freeBucket(&bucket); + } + + //ensure pops work correctly + { + //setup + Toy_Bucket* bucket = Toy_allocateBucket(TOY_BUCKET_IDEAL); + + //run + Toy_Scope* scope = NULL; + + for (int i = 0; i < 5; i++) { + scope = Toy_pushScope(&bucket, scope); + } + + for (int i = 0; i < 2; i++) { + scope = Toy_popScope(scope); + } + + //check + if ( + scope == NULL || + scope->next == NULL || + scope->table == NULL || + scope->table->capacity != 16 || + scope->refCount != 1 || + + scope->next->next == NULL || + scope->next->table == NULL || + scope->next->table->capacity != 16 || + scope->next->refCount != 2 || + + scope->next->next->next != NULL || + scope->next->next->table == NULL || + scope->next->next->table->capacity != 16 || + scope->next->next->refCount != 3 || + + false) + { + fprintf(stderr, TOY_CC_ERROR "ERROR: Failed to allocate and free a list of Toy_Scope\n" TOY_CC_RESET); + while (scope) { + scope = Toy_popScope(scope); + } + Toy_freeBucket(&bucket); + return -1; + } + + //cleanup + while (scope) { + scope = Toy_popScope(scope); + } + Toy_freeBucket(&bucket); + } + + //branched scope references + { + //setup + Toy_Bucket* bucket = Toy_allocateBucket(TOY_BUCKET_IDEAL); + + //run + Toy_Scope* scopeBase = Toy_pushScope(&bucket, NULL); + Toy_Scope* scopeA = Toy_pushScope(&bucket, scopeBase); + Toy_Scope* scopeB = Toy_pushScope(&bucket, scopeBase); + + //check + if ( + scopeBase == NULL || + scopeBase->next != NULL || + scopeBase->table == NULL || + scopeBase->table->capacity != 16 || + scopeBase->refCount != 3 || + + scopeA == NULL || + scopeA->next != scopeBase || + scopeA->table == NULL || + scopeA->table->capacity != 16 || + scopeA->refCount != 1 || + + scopeB == NULL || + scopeB->next != scopeBase || + scopeB->table == NULL || + scopeB->table->capacity != 16 || + scopeB->refCount != 1 || + + scopeA->next != scopeB->next || //double check + + false) + { + fprintf(stderr, TOY_CC_ERROR "ERROR: Failed to allocate branched scopes\n" TOY_CC_RESET); + Toy_popScope(scopeB); + Toy_popScope(scopeA); + Toy_popScope(scopeBase); + Toy_freeBucket(&bucket); + return -1; + } + + //cleanup + Toy_popScope(scopeB); + Toy_popScope(scopeA); + Toy_popScope(scopeBase); + + Toy_freeBucket(&bucket); + } + + //deallocate ancestors correctly + { + //setup + Toy_Bucket* bucket = Toy_allocateBucket(TOY_BUCKET_IDEAL); + + //run + Toy_Scope* scopeA = Toy_pushScope(&bucket, NULL); + Toy_Scope* scopeB = Toy_pushScope(&bucket, scopeA); + Toy_Scope* scopeC = Toy_pushScope(&bucket, scopeB); + + Toy_popScope(scopeB); + + //check + if ( + scopeA == NULL || + scopeA->next != NULL || + scopeA->table == NULL || + scopeA->table->capacity != 16 || + scopeA->refCount != 2 || + + //scopeB still exists in memory until scopeC is popped + scopeB == NULL || + scopeB->next != scopeA || + scopeB->table == NULL || + scopeB->table->capacity != 16 || + scopeB->refCount != 1 || + + scopeC == NULL || + scopeC->next != scopeB || + scopeC->table == NULL || + scopeC->table->capacity != 16 || + scopeC->refCount != 1 || + + false) + { + fprintf(stderr, TOY_CC_ERROR "ERROR: Failed to deallocate ancestors scopes correctly\n" TOY_CC_RESET); + Toy_popScope(scopeC); + Toy_popScope(scopeA); + Toy_freeBucket(&bucket); + return -1; + } + + //cleanup + Toy_popScope(scopeC); + Toy_popScope(scopeA); + + Toy_freeBucket(&bucket); + } + + //deep copy + { + //setup + Toy_Bucket* bucket = Toy_allocateBucket(TOY_BUCKET_IDEAL); + + //run + Toy_Scope* scopeA = Toy_pushScope(&bucket, NULL); + Toy_Scope* scopeB = Toy_pushScope(&bucket, scopeA); + Toy_Scope* scopeCopy = Toy_deepCopyScope(&bucket, scopeB); + + //check + if ( + scopeA == NULL || + scopeA->next != NULL || + scopeA->table == NULL || + scopeA->table->capacity != 16 || + scopeA->refCount != 3 || + + scopeB == NULL || + scopeB->next != scopeA || + scopeB->table == NULL || + scopeB->table->capacity != 16 || + scopeB->refCount != 1 || + + scopeB == NULL || + scopeB->next != scopeA || + scopeB->table == NULL || + scopeB->table->capacity != 16 || + scopeB->refCount != 1 || + + scopeB == scopeCopy || + + false) + { + fprintf(stderr, TOY_CC_ERROR "ERROR: Failed to deep copy a scope\n" TOY_CC_RESET); + Toy_popScope(scopeCopy); + Toy_popScope(scopeB); + Toy_popScope(scopeA); + Toy_freeBucket(&bucket); + return -1; + } + + //cleanup + Toy_popScope(scopeCopy); + Toy_popScope(scopeB); + Toy_popScope(scopeA); + + Toy_freeBucket(&bucket); + } + + return 0; +} + +int test_scope_elements() { + //TODO: Ensure the scope's primary function of handling key-value pairs works correctly. + printf(TOY_CC_WARN "'test_scope_elements()' not yet implemented\n" TOY_CC_RESET); + return 0; +} + +int main() { + //run each test set, returning the total errors given + int total = 0, res = 0; + + { + res = test_scope_allocation(); + if (res == 0) { + printf(TOY_CC_NOTICE "All good\n" TOY_CC_RESET); + } + total += res; + } + + { + res = test_scope_elements(); + if (res == 0) { + // printf(TOY_CC_NOTICE "All good\n" TOY_CC_RESET); + } + total += res; + } + + return total; +} diff --git a/tests/makefile b/tests/makefile index 132b0e8..d7e4262 100644 --- a/tests/makefile +++ b/tests/makefile @@ -23,7 +23,7 @@ TEST_OBJDIR=obj #file names TEST_SOURCEFILES=$(wildcard $(TEST_SOURCEDIR)/*.c) -TEST_CASESFILES=$(wildcard $(TEST_CASESDIR)/*.c) +TEST_CASESFILES=$(wildcard $(TEST_CASESDIR)/test_*.c) #build the object files, compile the test cases, and run all: clean build-source build-cases build-link build-run