Functions are working, tests incomplete

This required a massive cross-cutting rework to the scope system,
multiple subtle bugfixes and relearning of the parser internals, but it
does appear that functions are working correctly.

A few caveats: for now, parameters are always constant, regardless of
type, return values can't be specified, and some script tests have been
written.

Most importantly, a key feature is working: closures.
This commit is contained in:
2026-04-12 11:47:26 +10:00
parent b0d9c15d33
commit c0c03a4110
18 changed files with 158 additions and 92 deletions

View File

@@ -81,6 +81,8 @@ typedef enum Toy_AstFlag {
TOY_AST_FLAG_POSTFIX_INCREMENT = 43,
TOY_AST_FLAG_POSTFIX_DECREMENT = 44,
TOY_AST_FLAG_INVOKATION = 45,
// TOY_AST_FLAG_TERNARY,
} Toy_AstFlag;

View File

@@ -188,14 +188,16 @@ static unsigned int emitParameters(Toy_Bytecode* mb, Toy_Ast* ast) {
total += emitParameters(mb, ast->aggregate.right);
return total;
}
else if (ast->type != TOY_AST_VALUE) {
else if (ast->type != TOY_AST_VAR_DECLARE) {
fprintf(stderr, TOY_CC_ERROR "ERROR: Unknown AST type passed to 'emitParameters()'\n" TOY_CC_RESET);
exit(-1);
return 0;
}
//the address within the data section
unsigned int dataAddr = emitCStringToData(&(mb->data), &(mb->dataCapacity), &(mb->dataCount), TOY_VALUE_AS_STRING(ast->value.value)->leaf.data);
char buffer[128];
snprintf(buffer, 128, "%.*s", ast->varDeclare.name->info.length, ast->varDeclare.name->leaf.data);
unsigned int dataAddr = emitCStringToData(&(mb->data), &(mb->dataCapacity), &(mb->dataCount), buffer);
//check the param index for that entry i.e. don't reuse parameter names
for (unsigned int i = 0; i < mb->paramCount; i++) {
@@ -209,7 +211,7 @@ static unsigned int emitParameters(Toy_Bytecode* mb, Toy_Ast* ast) {
//emit to the param index
EMIT_INT(&mb, param, dataAddr);
EMIT_INT(&mb, param, ast->value.value.type);
EMIT_INT(&mb, param, ast->varDeclare.valueType); //'constant' is lost, but that's fine for params
//this returns the number of written parameters
return 1;

View File

@@ -1,10 +1,18 @@
#include "toy_function.h"
Toy_Function* Toy_createFunctionFromBytecode(Toy_Bucket** bucketHandle, unsigned char* bytecode) {
Toy_Function* Toy_createFunctionFromBytecode(Toy_Bucket** bucketHandle, unsigned char* bytecode, Toy_Scope* parentScope) {
Toy_Function* fn = (Toy_Function*)Toy_partitionBucket(bucketHandle, sizeof(Toy_Function));
fn->type = TOY_FUNCTION_CUSTOM;
fn->bytecode.code = bytecode;
fn->bytecode.parentScope = parentScope;
Toy_private_incrementScopeRefCount(fn->bytecode.parentScope);
return fn;
}
TOY_API void Toy_freeFunction(Toy_Function* fn) {
if (fn->type == TOY_FUNCTION_CUSTOM) {
Toy_private_decrementScopeRefCount(fn->bytecode.parentScope);
}
}

View File

@@ -2,6 +2,7 @@
#include "toy_common.h"
#include "toy_bucket.h"
#include "toy_scope.h"
typedef enum Toy_FunctionType {
TOY_FUNCTION_CUSTOM,
@@ -11,6 +12,7 @@ typedef enum Toy_FunctionType {
typedef struct Toy_FunctionBytecode {
Toy_FunctionType type;
unsigned char* code;
Toy_Scope* parentScope;
} Toy_FunctionBytecode;
typedef struct Toy_FunctionNative {
@@ -24,4 +26,6 @@ typedef union Toy_Function_t {
Toy_FunctionNative native;
} Toy_Function;
TOY_API Toy_Function* Toy_createFunctionFromBytecode(Toy_Bucket** bucketHandle, unsigned char* bytecode);
TOY_API Toy_Function* Toy_createFunctionFromBytecode(Toy_Bucket** bucketHandle, unsigned char* bytecode, Toy_Scope* parentScope);
TOY_API void Toy_freeFunction(Toy_Function* fn);

View File

@@ -731,7 +731,7 @@ static Toy_AstFlag invoke(Toy_Bucket** bucketHandle, Toy_Parser* parser, Toy_Ast
//finally, emit the call as an Ast
Toy_private_emitAstFunctionInvokation(bucketHandle, rootHandle, args);
return TOY_AST_FLAG_NONE;
return TOY_AST_FLAG_INVOKATION;
}
//grammar rules
@@ -964,25 +964,23 @@ static void makeFunctionDeclarationStmt(Toy_Bucket** bucketHandle, Toy_Parser* p
advance(parser);
Toy_Token nameToken = parser->previous;
//TODO: fix this with param type info
//URGENT: fix this with param type info
//read the type specifier if present
// Toy_ValueType varType = TOY_VALUE_ANY;
// bool constant = true; //parameters are immutable
Toy_ValueType varType = TOY_VALUE_ANY;
bool constant = true; //parameters are immutable
if (match(parser, TOY_TOKEN_OPERATOR_COLON)) {
// varType = readType(parser);
readType(parser);
varType = readType(parser);
if (match(parser, TOY_TOKEN_KEYWORD_CONST)) {
// constant = true;
constant = true;
}
}
//emit the parameter as a name string
Toy_String* name = Toy_toStringLength(bucketHandle, nameToken.lexeme, nameToken.length);
Toy_Value value = TOY_VALUE_FROM_STRING(name);
//emit the parameter as a var declaration
Toy_Ast* ast = NULL;
Toy_private_emitAstValue(bucketHandle, &ast, value); //TODO: params with type info
Toy_String* name = Toy_toStringLength(bucketHandle, nameToken.lexeme, nameToken.length);
Toy_private_emitAstVariableDeclaration(bucketHandle, &ast, name, varType, constant, NULL);
//add to the params aggregate (is added backwards, because weird)
Toy_private_emitAstAggregate(bucketHandle, &params, TOY_AST_FLAG_COLLECTION, ast);

View File

@@ -8,34 +8,6 @@
#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->data != NULL) {
//free the scope entries when this scope is no longer needed
for (unsigned int i = 0; i < iter->capacity; i++) {
if (iter->data[i].psl > 0) {
Toy_freeString(&(iter->data[i].key));
Toy_freeValue(iter->data[i].value);
}
}
free(iter->data);
}
}
}
static Toy_ScopeEntry* lookupScopeEntryPtr(Toy_Scope* scope, Toy_String* key, unsigned int hash, bool recursive) {
//terminate
if (scope == NULL || scope->data == NULL) {
@@ -61,7 +33,7 @@ static Toy_ScopeEntry* lookupScopeEntryPtr(Toy_Scope* scope, Toy_String* key, un
}
}
void probeAndInsert(Toy_Scope* scope, Toy_String* key, Toy_Value value, Toy_ValueType type, bool constant) {
static void probeAndInsert(Toy_Scope* scope, Toy_String* key, Toy_Value value, Toy_ValueType type, bool constant) {
//make the entry
unsigned int probe = Toy_hashString(key) % scope->capacity;
Toy_ScopeEntry entry = (Toy_ScopeEntry){ .key = *key, .value = value, .type = type, .constant = constant, .psl = 1 };
@@ -97,7 +69,7 @@ void probeAndInsert(Toy_Scope* scope, Toy_String* key, Toy_Value value, Toy_Valu
}
}
Toy_ScopeEntry* adjustScopeEntries(Toy_Scope* scope, unsigned int newCapacity) {
static Toy_ScopeEntry* adjustScopeEntries(Toy_Scope* scope, unsigned int newCapacity) {
//allocate and zero a new Toy_ScopeEntry array in memory
Toy_ScopeEntry* newEntries = malloc(newCapacity * sizeof(Toy_ScopeEntry));
@@ -142,7 +114,7 @@ Toy_Scope* Toy_pushScope(Toy_Bucket** bucketHandle, Toy_Scope* scope) {
newScope->count = 0;
newScope->maxPsl = 0;
incrementRefCount(newScope);
Toy_private_incrementScopeRefCount(newScope);
return newScope;
}
@@ -152,7 +124,7 @@ Toy_Scope* Toy_popScope(Toy_Scope* scope) {
return NULL;
}
decrementRefCount(scope);
Toy_private_decrementScopeRefCount(scope);
return scope->next;
}
@@ -169,7 +141,7 @@ void Toy_declareScope(Toy_Scope* scope, Toy_String* key, Toy_ValueType type, Toy
//type check
if (type != TOY_VALUE_ANY && value.type != TOY_VALUE_NULL && type != 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->leaf.data, Toy_private_getValueTypeAsCString(type), Toy_private_getValueTypeAsCString(value.type));
sprintf(buffer, "Incorrect value type in declaration of '%.*s' (expected %s, got %s)", key->info.length, key->leaf.data, Toy_private_getValueTypeAsCString(type), Toy_private_getValueTypeAsCString(value.type));
Toy_error(buffer);
return;
}
@@ -190,7 +162,7 @@ void Toy_assignScope(Toy_Scope* scope, Toy_String* key, Toy_Value value) {
//type check
if (entryPtr->type != TOY_VALUE_ANY && value.type != TOY_VALUE_NULL && entryPtr->type != 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->leaf.data, Toy_private_getValueTypeAsCString(entryPtr->type), Toy_private_getValueTypeAsCString(value.type));
sprintf(buffer, "Incorrect value type in assignment of '%.*s' (expected %s, got %s)", key->info.length, key->leaf.data, Toy_private_getValueTypeAsCString(entryPtr->type), Toy_private_getValueTypeAsCString(value.type));
Toy_error(buffer);
return;
}
@@ -223,3 +195,31 @@ bool Toy_isDeclaredScope(Toy_Scope* scope, Toy_String* key) {
Toy_ScopeEntry* entryPtr = lookupScopeEntryPtr(scope, key, Toy_hashString(key), true);
return entryPtr != NULL;
}
void Toy_private_incrementScopeRefCount(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++;
}
}
void Toy_private_decrementScopeRefCount(Toy_Scope* scope) {
for (Toy_Scope* iter = scope; iter; iter = iter->next) {
iter->refCount--;
if (iter->refCount == 0 && iter->data != NULL) {
//free the scope entries when this scope is no longer needed
for (unsigned int i = 0; i < iter->capacity; i++) {
if (iter->data[i].psl > 0) {
Toy_freeString(&(iter->data[i].key));
Toy_freeValue(iter->data[i].value);
}
}
free(iter->data);
}
}
}

View File

@@ -36,6 +36,10 @@ TOY_API Toy_Value* Toy_accessScopeAsPointer(Toy_Scope* scope, Toy_String* key);
TOY_API bool Toy_isDeclaredScope(Toy_Scope* scope, Toy_String* key);
//manage refcounting
TOY_API void Toy_private_incrementScopeRefCount(Toy_Scope* scope);
TOY_API void Toy_private_decrementScopeRefCount(Toy_Scope* scope);
//some useful sizes, could be swapped out as needed
#ifndef TOY_SCOPE_INITIAL_CAPACITY
#define TOY_SCOPE_INITIAL_CAPACITY 8

View File

@@ -178,7 +178,7 @@ void Toy_freeValue(Toy_Value value) {
return;
case TOY_VALUE_FUNCTION:
//not sure this needs to be freed
Toy_freeFunction(value.as.function);
return;
case TOY_VALUE_OPAQUE:

View File

@@ -151,14 +151,15 @@ static void processRead(Toy_VM* vm) {
}
case TOY_VALUE_FUNCTION: {
// unsigned int paramCount = (unsigned int)READ_BYTE(vm); //unused
unsigned int paramCount = (unsigned int)READ_BYTE(vm); //unused
(void)paramCount;
fixAlignment(vm);
unsigned int addr = (unsigned int)READ_INT(vm);
//create and push the function value
Toy_Function* function = Toy_createFunctionFromBytecode(&vm->memoryBucket, vm->code + vm->subsAddr + addr);
Toy_Function* function = Toy_createFunctionFromBytecode(&vm->memoryBucket, vm->code + vm->subsAddr + addr, vm->scope);
value = TOY_VALUE_FROM_FUNCTION(function);
break;
@@ -396,7 +397,7 @@ static void processInvoke(Toy_VM* vm) {
//spin up a new sub-vm
Toy_VM subVM;
Toy_inheritVM(&subVM, vm);
Toy_bindVM(&subVM, fn->bytecode.code, false);
Toy_bindVM(&subVM, fn->bytecode.code, fn->bytecode.parentScope);
//check args count
if (argCount * 8 != subVM.paramCount) {
@@ -1104,7 +1105,7 @@ void Toy_inheritVM(Toy_VM* vm, Toy_VM* parent) {
Toy_resetVM(vm, true);
}
void Toy_bindVM(Toy_VM* vm, unsigned char* bytecode, bool preserveScope) {
void Toy_bindVM(Toy_VM* vm, unsigned char* bytecode, Toy_Scope* parentScope) {
vm->code = bytecode; //set code, so it can be read
(void)READ_UNSIGNED_INT(vm); //global header
@@ -1131,8 +1132,8 @@ void Toy_bindVM(Toy_VM* vm, unsigned char* bytecode, bool preserveScope) {
}
//scopes
if (preserveScope == false) {
vm->scope = Toy_pushScope(&vm->memoryBucket, NULL);
if (vm->scope == NULL) {
vm->scope = Toy_pushScope(&vm->memoryBucket, parentScope);
}
}

View File

@@ -47,7 +47,7 @@ TOY_API void Toy_resetVM(Toy_VM* vm, bool preserveScope);
TOY_API void Toy_initVM(Toy_VM* vm); //creates memory
TOY_API void Toy_inheritVM(Toy_VM* vm, Toy_VM* parent); //inherits scope bucket
void Toy_bindVM(Toy_VM* vm, unsigned char* bytecode, bool preserveScope);
void Toy_bindVM(Toy_VM* vm, unsigned char* bytecode, Toy_Scope* parentScope);
TOY_API unsigned int Toy_runVM(Toy_VM* vm);
TOY_API void Toy_freeVM(Toy_VM* vm);