Files
Toy/tests/cases/test_scope.c
Kayne Ruse d3b59eb0da WIP functions working, untested, memory issues, read more
I've ripped out the deep copying, and flattened the bucket usage, but
this results in no memory being freed or reused for the lifetime of the
program.

This is shown most clearly with this script:

```toy
fn makeCounter() {
	var counter: int = 0;

	fn increment() {
		return ++counter;
	}

	return increment;
}

var tally = makeCounter();

while (true) {
	var result = tally();

	if (result >= 10_000_000) {
		break;
	}
}
```

The number of calls vs amount of memory consumed is:

```
function calls -> memory used in megabytes
1_000 -> 0.138128
10_000 -> 2.235536
100_000 -> 21.7021
1_000_000 -> 216.1712
10_000_000 -> 1520.823
```

Obviously this needs to be fixed, as ballooning to gigabytes of memory
in only a moment isn't practical. That will be the next task - to find
some way to free memory that isn't needed anymore.

See #163, #160
2025-02-21 11:41:27 +11:00

464 lines
11 KiB
C

#include "toy_scope.h"
#include "toy_console_colors.h"
#include "toy_bucket.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int test_scope_allocation(void) {
//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 != 8 ||
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 != 8 ||
scope->refCount != 1 ||
scope->next->next == NULL ||
scope->next->table == NULL ||
scope->next->table->capacity != 8 ||
scope->next->refCount != 2 ||
scope->next->next->next == NULL ||
scope->next->next->table == NULL ||
scope->next->next->table->capacity != 8 ||
scope->next->next->refCount != 3 ||
scope->next->next->next->next == NULL ||
scope->next->next->next->table == NULL ||
scope->next->next->next->table->capacity != 8 ||
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 != 8 ||
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 != 8 ||
scope->refCount != 1 ||
scope->next->next == NULL ||
scope->next->table == NULL ||
scope->next->table->capacity != 8 ||
scope->next->refCount != 2 ||
scope->next->next->next != NULL ||
scope->next->next->table == NULL ||
scope->next->next->table->capacity != 8 ||
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 != 8 ||
scopeBase->refCount != 3 ||
scopeA == NULL ||
scopeA->next != scopeBase ||
scopeA->table == NULL ||
scopeA->table->capacity != 8 ||
scopeA->refCount != 1 ||
scopeB == NULL ||
scopeB->next != scopeBase ||
scopeB->table == NULL ||
scopeB->table->capacity != 8 ||
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 != 8 ||
scopeA->refCount != 2 ||
//scopeB still exists in memory until scopeC is popped
scopeB == NULL ||
scopeB->next != scopeA ||
scopeB->table == NULL ||
scopeB->table->capacity != 8 ||
scopeB->refCount != 1 ||
scopeC == NULL ||
scopeC->next != scopeB ||
scopeC->table == NULL ||
scopeC->table->capacity != 8 ||
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);
}
return 0;
}
int test_scope_elements(void) {
//allocate, access and assign an element
{
//setup
Toy_Bucket* bucket = Toy_allocateBucket(TOY_BUCKET_IDEAL);
Toy_Scope* scope = Toy_pushScope(&bucket, NULL);
Toy_String* hello1 = Toy_createNameStringLength(&bucket, "hello", 5, TOY_VALUE_ANY, false);
Toy_String* hello2 = Toy_createNameStringLength(&bucket, "hello", 5, TOY_VALUE_ANY, false);
//check nothing is here
if (Toy_isDeclaredScope(scope, hello2)) {
fprintf(stderr, TOY_CC_ERROR "ERROR: Unexpected entry found in Toy_Scope\n" TOY_CC_RESET);
Toy_freeString(hello2);
Toy_freeString(hello1);
Toy_popScope(scope);
Toy_freeBucket(&bucket);
return -1;
}
//declare and access values
Toy_declareScope(scope, hello1, TOY_VALUE_FROM_INTEGER(42));
if (!Toy_isDeclaredScope(scope, hello2)) {
fprintf(stderr, TOY_CC_ERROR "ERROR: Unexpected missing entry in Toy_Scope\n" TOY_CC_RESET);
Toy_freeString(hello2);
Toy_freeString(hello1);
Toy_popScope(scope);
Toy_freeBucket(&bucket);
return -1;
}
Toy_Value* result = Toy_accessScopeAsPointer(scope, hello2);
//check integer
if (scope == NULL ||
scope->next != NULL ||
scope->table == NULL ||
scope->table->capacity != 8 ||
scope->refCount != 1 ||
TOY_VALUE_IS_INTEGER(*result) != true ||
TOY_VALUE_AS_INTEGER(*result) != 42 ||
false)
{
fprintf(stderr, TOY_CC_ERROR "ERROR: Failed to declare in Toy_Scope\n" TOY_CC_RESET);
Toy_freeString(hello2);
Toy_freeString(hello1);
Toy_popScope(scope);
Toy_freeBucket(&bucket);
return -1;
}
//assign values
Toy_assignScope(scope, hello1, TOY_VALUE_FROM_FLOAT(3.1415f));
Toy_Value* resultTwo = Toy_accessScopeAsPointer(scope, hello2);
//check float
if (scope == NULL ||
scope->next != NULL ||
scope->table == NULL ||
scope->table->capacity != 8 ||
scope->refCount != 1 ||
TOY_VALUE_IS_FLOAT(*resultTwo) != true ||
TOY_VALUE_AS_FLOAT(*resultTwo) != 3.1415f ||
false)
{
fprintf(stderr, TOY_CC_ERROR "ERROR: Failed to assign in Toy_Scope\n" TOY_CC_RESET);
Toy_freeString(hello2);
Toy_freeString(hello1);
Toy_popScope(scope);
Toy_freeBucket(&bucket);
return -1;
}
//cleanup
Toy_freeString(hello2);
Toy_freeString(hello1);
Toy_popScope(scope);
Toy_freeBucket(&bucket);
}
//find an entry in an ancestor scope
{
//setup
Toy_Bucket* bucket = Toy_allocateBucket(TOY_BUCKET_IDEAL);
Toy_Scope* scope = Toy_pushScope(&bucket, NULL);
Toy_String* hello = Toy_createNameStringLength(&bucket, "hello", 5, TOY_VALUE_ANY, false);
//declare and push
Toy_declareScope(scope, hello, TOY_VALUE_FROM_INTEGER(42));
scope = Toy_pushScope(&bucket, scope);
scope = Toy_pushScope(&bucket, scope);
{
//check it's accessible
Toy_Value* result1 = Toy_accessScopeAsPointer(scope, hello);
if (TOY_VALUE_IS_INTEGER(*result1) != true ||
TOY_VALUE_AS_INTEGER(*result1) != 42)
{
fprintf(stderr, TOY_CC_ERROR "ERROR: Failed to access from an ancestor Toy_Scope\n" TOY_CC_RESET);
Toy_freeString(hello);
while ((scope = Toy_popScope(scope)) != NULL) /* */;
Toy_freeBucket(&bucket);
return -1;
}
}
Toy_declareScope(scope, hello, TOY_VALUE_FROM_FLOAT(3.1415f));
{
//check it's shadowed correctly
Toy_Value* result2 = Toy_accessScopeAsPointer(scope, hello);
if (TOY_VALUE_IS_FLOAT(*result2) != true ||
TOY_VALUE_AS_FLOAT(*result2) != 3.1415f)
{
fprintf(stderr, TOY_CC_ERROR "ERROR: Failed to shadow an entry in Toy_Scope\n" TOY_CC_RESET);
Toy_freeString(hello);
while ((scope = Toy_popScope(scope)) != NULL) /* */;
Toy_freeBucket(&bucket);
return -1;
}
}
scope = Toy_popScope(scope);
{
//check it's recovered correctly
Toy_Value* result3 = Toy_accessScopeAsPointer(scope, hello);
if (TOY_VALUE_IS_INTEGER(*result3) != true ||
TOY_VALUE_AS_INTEGER(*result3) != 42)
{
fprintf(stderr, TOY_CC_ERROR "ERROR: Failed to recover an entry in Toy_Scope\n" TOY_CC_RESET);
Toy_freeString(hello);
while ((scope = Toy_popScope(scope)) != NULL) /* */;
Toy_freeBucket(&bucket);
return -1;
}
}
Toy_assignScope(scope, hello, TOY_VALUE_FROM_INTEGER(8891));
{
//check it's assigned correctly
Toy_Value* result4 = Toy_accessScopeAsPointer(scope, hello);
if (TOY_VALUE_IS_INTEGER(*result4) != true ||
TOY_VALUE_AS_INTEGER(*result4) != 8891)
{
fprintf(stderr, TOY_CC_ERROR "ERROR: Failed to assign to an ancestor in Toy_Scope\n" TOY_CC_RESET);
Toy_freeString(hello);
while ((scope = Toy_popScope(scope)) != NULL) /* */;
Toy_freeBucket(&bucket);
return -1;
}
}
scope = Toy_popScope(scope);
{
//check it's in the correct state
Toy_Value* result5 = Toy_accessScopeAsPointer(scope, hello);
if (TOY_VALUE_IS_INTEGER(*result5) != true ||
TOY_VALUE_AS_INTEGER(*result5) != 8891)
{
fprintf(stderr, TOY_CC_ERROR "ERROR: Failed to access an altered entry of an ancestor in Toy_Scope\n" TOY_CC_RESET);
Toy_freeString(hello);
while ((scope = Toy_popScope(scope)) != NULL) /* */;
Toy_freeBucket(&bucket);
return -1;
}
}
//cleanup
Toy_freeString(hello);
while ((scope = Toy_popScope(scope)) != NULL) /* */;
Toy_freeBucket(&bucket);
}
return 0;
}
int main(void) {
//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;
}