mirror of
https://github.com/krgamestudios/Toy.git
synced 2026-04-15 14:54:07 +10:00
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
464 lines
11 KiB
C
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;
|
|
}
|