Files
Toy/source/toy_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

192 lines
5.5 KiB
C

#include "toy_scope.h"
#include "toy_console_colors.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "toy_print.h"
//utils
static void incrementRefCount(Toy_Scope* scope) {
for (Toy_Scope* iter = scope; iter; iter = iter->next) {
//check for issues
if (iter->next != NULL && iter->next->refCount == 0) {
fprintf(stderr, TOY_CC_ERROR "ERROR: Toy_Scope's ancestor has a refcount of 0'\n" TOY_CC_RESET);
exit(-1);
}
iter->refCount++;
}
}
static void decrementRefCount(Toy_Scope* scope) {
for (Toy_Scope* iter = scope; iter; iter = iter->next) {
iter->refCount--;
if (iter->refCount == 0 && iter->table != NULL) {
Toy_freeTable(iter->table);
iter->table = NULL;
}
}
}
static Toy_TableEntry* lookupScope(Toy_Scope* scope, Toy_String* key, unsigned int hash, bool recursive) {
//terminate
if (scope == NULL) {
return NULL;
}
//continue after a dummy
if (scope->table == NULL) {
return recursive ? lookupScope(scope->next, key, hash, recursive) : NULL;
}
//copy and modify the code from Toy_lookupTable, so it can behave slightly differently
unsigned int probe = hash % scope->table->capacity;
while (true) {
//found the entry
if (TOY_VALUE_IS_STRING(scope->table->data[probe].key) && Toy_compareStrings(TOY_VALUE_AS_STRING(scope->table->data[probe].key), key) == 0) {
return &(scope->table->data[probe]);
}
//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;
}
//adjust and continue
probe = (probe + 1) % scope->table->capacity;
}
}
//exposed functions
Toy_Scope* Toy_pushScope(Toy_Bucket** bucketHandle, Toy_Scope* scope) {
Toy_Scope* newScope = (Toy_Scope*)Toy_partitionBucket(bucketHandle, sizeof(Toy_Scope));
newScope->next = scope;
newScope->table = Toy_allocateTable();
newScope->refCount = 0;
incrementRefCount(newScope);
return newScope;
}
Toy_Scope* Toy_private_pushDummyScope(Toy_Bucket** bucketHandle, Toy_Scope* scope) {
Toy_Scope* newScope = (Toy_Scope*)Toy_partitionBucket(bucketHandle, sizeof(Toy_Scope));
newScope->next = scope;
newScope->table = NULL;
newScope->refCount = 0;
incrementRefCount(newScope);
return newScope;
}
Toy_Scope* Toy_popScope(Toy_Scope* scope) {
if (scope == NULL) {
return NULL;
}
decrementRefCount(scope);
return scope->next;
}
void Toy_declareScope(Toy_Scope* scope, Toy_String* key, Toy_Value value) {
if (key->info.type != TOY_STRING_NAME) {
fprintf(stderr, TOY_CC_ERROR "ERROR: Toy_Scope only allows name strings as keys\n" TOY_CC_RESET);
exit(-1);
}
if (scope->table == NULL) {
fprintf(stderr, TOY_CC_ERROR "ERROR: Can't declare in a dummy scope\n" TOY_CC_RESET);
exit(-1);
}
Toy_TableEntry* entryPtr = lookupScope(scope, key, Toy_hashString(key), false);
if (entryPtr != NULL) {
char buffer[key->info.length + 256];
sprintf(buffer, "Can't redefine a variable: %s", key->name.data);
Toy_error(buffer);
return;
}
//type check
Toy_ValueType kt = Toy_getNameStringVarType(key);
if (kt != TOY_VALUE_ANY && value.type != TOY_VALUE_NULL && kt != value.type && value.type != TOY_VALUE_REFERENCE) {
char buffer[key->info.length + 256];
sprintf(buffer, "Incorrect value type in declaration of '%s' (expected %s, got %s)", key->name.data, Toy_private_getValueTypeAsCString(kt), Toy_private_getValueTypeAsCString(value.type));
Toy_error(buffer);
return;
}
Toy_insertTable(&scope->table, TOY_VALUE_FROM_STRING(Toy_copyString(key)), value);
}
void Toy_assignScope(Toy_Scope* scope, Toy_String* key, Toy_Value value) {
if (key->info.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_TableEntry* entryPtr = lookupScope(scope, key, Toy_hashString(key), true);
if (entryPtr == NULL) {
char buffer[key->info.length + 256];
sprintf(buffer, "Undefined variable: %s\n", key->name.data);
Toy_error(buffer);
return;
}
//type check
Toy_ValueType kt = Toy_getNameStringVarType( TOY_VALUE_AS_STRING(entryPtr->key) );
if (kt != TOY_VALUE_ANY && value.type != TOY_VALUE_NULL && kt != value.type && value.type != TOY_VALUE_REFERENCE) {
char buffer[key->info.length + 256];
sprintf(buffer, "Incorrect value type in assignment of '%s' (expected %s, got %s)", key->name.data, Toy_private_getValueTypeAsCString(kt), Toy_private_getValueTypeAsCString(value.type));
Toy_error(buffer);
return;
}
//constness check
if (Toy_getNameStringVarConstant( TOY_VALUE_AS_STRING(entryPtr->key) )) {
char buffer[key->info.length + 256];
sprintf(buffer, "Can't reassign to constant variable %s", key->name.data);
Toy_error(buffer);
return;
}
entryPtr->value = value;
}
Toy_Value* Toy_accessScopeAsPointer(Toy_Scope* scope, Toy_String* key) {
if (key->info.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_TableEntry* entryPtr = lookupScope(scope, key, Toy_hashString(key), true);
if (entryPtr == NULL) {
char buffer[key->info.length + 256];
sprintf(buffer, "Undefined variable: %s\n", key->name.data);
Toy_error(buffer);
return NULL;
}
return &(entryPtr->value);
}
bool Toy_isDeclaredScope(Toy_Scope* scope, Toy_String* key) {
if (key->info.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_TableEntry* entryPtr = lookupScope(scope, key, Toy_hashString(key), true);
return entryPtr != NULL;
}