From 639250f028d2d9cb9ef8089e0adec90e9a2d9b44 Mon Sep 17 00:00:00 2001 From: Kayne Ruse Date: Mon, 17 Feb 2025 19:07:14 +1100 Subject: [PATCH] WIP return keyword, read more Functions are having issues with being copied around, especially between buckets, leading to the scopes getting looped. The program gets stuck in 'incrementRefCount()'. It's past my time limit, so I'll keep working on it tomorrow with a fresh mind. All function stuff is still untested. See #163 --- scripts/funky.toy | 24 ++++++++++-- source/toy_ast.c | 9 +++++ source/toy_ast.h | 8 ++++ source/toy_module_compiler.c | 19 +++++++++ source/toy_parser.c | 19 +++++++-- source/toy_scope.c | 6 +++ source/toy_scope.h | 2 + source/toy_value.c | 67 +++++++++++++++++++++++++++++++- source/toy_value.h | 1 + source/toy_vm.c | 75 ++++++++++++++++++++++++++++-------- source/toy_vm.h | 10 +++-- tests/cases/test_ast.c | 1 + 12 files changed, 215 insertions(+), 26 deletions(-) diff --git a/scripts/funky.toy b/scripts/funky.toy index 40a006d..e8f74b5 100644 --- a/scripts/funky.toy +++ b/scripts/funky.toy @@ -1,3 +1,5 @@ +/* + fn name(param1: int, param2: float, param3: string, param4) { print param1; print param2; @@ -7,8 +9,6 @@ fn name(param1: int, param2: float, param3: string, param4) { name(42, 3.14, "hello world", -1); -//URGENT: return values are still needed - fn output(arg) { print arg; } @@ -20,4 +20,22 @@ output(3.1415); output("woot!"); output([1, 23, 3]); output(["key":1]); -output(name); \ No newline at end of file +output(name); + +*/ + +fn makeCounter() { + var counter: int = 0; + + fn increment() { + return counter++; + } + + return increment; +} + +var tally = makeCounter(); + +print tally(); +print tally(); +print tally(); \ No newline at end of file diff --git a/source/toy_ast.c b/source/toy_ast.c index ed6441a..31d45dd 100644 --- a/source/toy_ast.c +++ b/source/toy_ast.c @@ -163,6 +163,15 @@ void Toy_private_emitAstContinue(Toy_Bucket** bucketHandle, Toy_Ast** astHandle) (*astHandle) = tmp; } +void Toy_private_emitAstReturn(Toy_Bucket** bucketHandle, Toy_Ast** astHandle) { + Toy_Ast* tmp = (Toy_Ast*)Toy_partitionBucket(bucketHandle, sizeof(Toy_Ast)); + + tmp->type = TOY_AST_RETURN; + tmp->fnReturn.child = (*astHandle); + + (*astHandle) = tmp; +} + void Toy_private_emitAstPrint(Toy_Bucket** bucketHandle, Toy_Ast** astHandle) { Toy_Ast* tmp = (Toy_Ast*)Toy_partitionBucket(bucketHandle, sizeof(Toy_Ast)); diff --git a/source/toy_ast.h b/source/toy_ast.h index c9f0384..3536b74 100644 --- a/source/toy_ast.h +++ b/source/toy_ast.h @@ -24,6 +24,7 @@ typedef enum Toy_AstType { TOY_AST_WHILE_THEN, TOY_AST_BREAK, TOY_AST_CONTINUE, + TOY_AST_RETURN, TOY_AST_PRINT, TOY_AST_VAR_DECLARE, @@ -171,6 +172,11 @@ typedef struct Toy_AstContinue { Toy_AstType type; } Toy_AstContinue; +typedef struct Toy_AstReturn { + Toy_AstType type; + Toy_Ast* child; +} Toy_AstReturn; + typedef struct Toy_AstPrint { Toy_AstType type; Toy_Ast* child; @@ -235,6 +241,7 @@ union Toy_Ast { //see 'test_ast.c' for bitness tests Toy_AstWhileThen whileThen; Toy_AstBreak breakPoint; Toy_AstContinue continuePoint; + Toy_AstReturn fnReturn; Toy_AstPrint print; Toy_AstVarDeclare varDeclare; Toy_AstVarAssign varAssign; @@ -263,6 +270,7 @@ void Toy_private_emitAstIfThenElse(Toy_Bucket** bucketHandle, Toy_Ast** astHandl void Toy_private_emitAstWhileThen(Toy_Bucket** bucketHandle, Toy_Ast** astHandle, Toy_Ast* condBranch, Toy_Ast* thenBranch); void Toy_private_emitAstBreak(Toy_Bucket** bucketHandle, Toy_Ast** rootHandle); void Toy_private_emitAstContinue(Toy_Bucket** bucketHandle, Toy_Ast** rootHandle); +void Toy_private_emitAstReturn(Toy_Bucket** bucketHandle, Toy_Ast** astHandle); void Toy_private_emitAstPrint(Toy_Bucket** bucketHandle, Toy_Ast** astHandle); void Toy_private_emitAstVariableDeclaration(Toy_Bucket** bucketHandle, Toy_Ast** astHandle, Toy_String* name, Toy_Ast* expr); diff --git a/source/toy_module_compiler.c b/source/toy_module_compiler.c index 45de6e4..3088dc8 100644 --- a/source/toy_module_compiler.c +++ b/source/toy_module_compiler.c @@ -752,6 +752,21 @@ static unsigned int writeInstructionContinue(Toy_ModuleCompiler** mb, Toy_AstCon return 0; } +static unsigned int writeInstructionReturn(Toy_ModuleCompiler** mb, Toy_AstReturn ast) { + //the things to return + unsigned int retCount = writeModuleCompilerCode(mb, ast.child); + + //output the print opcode + EMIT_BYTE(mb, code,TOY_OPCODE_RETURN); + + //4-byte alignment + EMIT_BYTE(mb, code,(unsigned char)retCount); + EMIT_BYTE(mb, code,0); + EMIT_BYTE(mb, code,0); + + return 0; +} + static unsigned int writeInstructionPrint(Toy_ModuleCompiler** mb, Toy_AstPrint ast) { //the thing to print writeModuleCompilerCode(mb, ast.child); @@ -1158,6 +1173,10 @@ static unsigned int writeModuleCompilerCode(Toy_ModuleCompiler** mb, Toy_Ast* as result += writeInstructionContinue(mb, ast->continuePoint); break; + case TOY_AST_RETURN: + result += writeInstructionReturn(mb, ast->fnReturn); + break; + case TOY_AST_PRINT: result += writeInstructionPrint(mb, ast->print); break; diff --git a/source/toy_parser.c b/source/toy_parser.c index 5a3133e..f6ba6b4 100644 --- a/source/toy_parser.c +++ b/source/toy_parser.c @@ -881,6 +881,13 @@ static void makeContinueStmt(Toy_Bucket** bucketHandle, Toy_Parser* parser, Toy_ consume(parser, TOY_TOKEN_OPERATOR_SEMICOLON, "Expected ';' at the end of continue statement"); } +static void makeReturnStmt(Toy_Bucket** bucketHandle, Toy_Parser* parser, Toy_Ast** rootHandle) { + parsePrecedence(bucketHandle, parser, rootHandle, PREC_GROUP); //expect an aggregate + Toy_private_emitAstReturn(bucketHandle, rootHandle); + + consume(parser, TOY_TOKEN_OPERATOR_SEMICOLON, "Expected ';' at the end of return statement"); +} + static void makePrintStmt(Toy_Bucket** bucketHandle, Toy_Parser* parser, Toy_Ast** rootHandle) { makeExpr(bucketHandle, parser, rootHandle); Toy_private_emitAstPrint(bucketHandle, rootHandle); @@ -1026,9 +1033,7 @@ static void makeStmt(Toy_Bucket** bucketHandle, Toy_Parser* parser, Toy_Ast** ro return; } - //for-pre-clause-post-then - //return - //import + //TODO: for-pre-clause-post-then //break else if (match(parser, TOY_TOKEN_KEYWORD_BREAK)) { @@ -1042,6 +1047,14 @@ static void makeStmt(Toy_Bucket** bucketHandle, Toy_Parser* parser, Toy_Ast** ro return; } + //return + else if (match(parser, TOY_TOKEN_KEYWORD_RETURN)) { + makeReturnStmt(bucketHandle, parser, rootHandle); + return; + } + + //TODO: import + //print else if (match(parser, TOY_TOKEN_KEYWORD_PRINT)) { makePrintStmt(bucketHandle, parser, rootHandle); diff --git a/source/toy_scope.c b/source/toy_scope.c index a0a9ff2..746c654 100644 --- a/source/toy_scope.c +++ b/source/toy_scope.c @@ -10,6 +10,12 @@ //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++; } } diff --git a/source/toy_scope.h b/source/toy_scope.h index ff88f7f..9766cf3 100644 --- a/source/toy_scope.h +++ b/source/toy_scope.h @@ -27,3 +27,5 @@ TOY_API void Toy_assignScope(Toy_Scope* scope, Toy_String* key, Toy_Value value) TOY_API Toy_Value* Toy_accessScopeAsPointer(Toy_Scope* scope, Toy_String* key); TOY_API bool Toy_isDeclaredScope(Toy_Scope* scope, Toy_String* key); + +//TODO: delcare with a custom table (game engine entities) \ No newline at end of file diff --git a/source/toy_value.c b/source/toy_value.c index 0668614..1452185 100644 --- a/source/toy_value.c +++ b/source/toy_value.c @@ -5,6 +5,7 @@ #include "toy_string.h" #include "toy_array.h" #include "toy_table.h" +#include "toy_function.h" #include #include @@ -136,7 +137,7 @@ Toy_Value Toy_copyValue(Toy_Value value) { return TOY_VALUE_FROM_TABLE(result); } case TOY_VALUE_FUNCTION: - return value; + // return value; //URGENT: concerning case TOY_VALUE_OPAQUE: case TOY_VALUE_ANY: @@ -150,6 +151,70 @@ Toy_Value Toy_copyValue(Toy_Value value) { return TOY_VALUE_FROM_NULL(); } +Toy_Value Toy_deepCopyValue(struct Toy_Bucket** bucketHandle, Toy_Value value) { + //this should be the same as Toy_copyValue(), but it forces a deep copy for the strings + MAYBE_UNWRAP(value); + + switch(value.type) { + case TOY_VALUE_NULL: + case TOY_VALUE_BOOLEAN: + case TOY_VALUE_INTEGER: + case TOY_VALUE_FLOAT: + return value; + + case TOY_VALUE_STRING: { + return TOY_VALUE_FROM_STRING(Toy_deepCopyString(bucketHandle, value.as.string)); + } + + case TOY_VALUE_ARRAY: { + //arrays probably won't get copied much + Toy_Array* ptr = value.as.array; + Toy_Array* result = Toy_resizeArray(NULL, ptr->capacity); + + for (unsigned int i = 0; i < ptr->count; i++) { + result->data[i] = Toy_deepCopyValue(bucketHandle, ptr->data[i]); + } + + result->capacity = ptr->capacity; + result->count = ptr->count; + + return TOY_VALUE_FROM_ARRAY(result); + } + + case TOY_VALUE_TABLE: { + //tables probably won't get copied much + Toy_Table* ptr = value.as.table; + Toy_Table* result = Toy_private_adjustTableCapacity(NULL, ptr->capacity); + + for (unsigned int i = 0; i < ptr->capacity; i++) { + if (TOY_VALUE_IS_NULL(ptr->data[i].key) != true) { + result->data[i].key = Toy_deepCopyValue(bucketHandle, ptr->data[i].key); + result->data[i].value = Toy_deepCopyValue(bucketHandle, ptr->data[i].value); + } + } + + result->capacity = ptr->capacity; + result->count = ptr->count; + + return TOY_VALUE_FROM_TABLE(result); + } + case TOY_VALUE_FUNCTION: { + Toy_Function* fn = Toy_createModuleFunction(bucketHandle, TOY_VALUE_AS_FUNCTION(value)->module.module); //URGENT: concerning + return TOY_VALUE_FROM_FUNCTION(fn); + } + + case TOY_VALUE_OPAQUE: + case TOY_VALUE_ANY: + case TOY_VALUE_REFERENCE: + case TOY_VALUE_UNKNOWN: + fprintf(stderr, TOY_CC_ERROR "ERROR: Can't deep-copy an unknown value type, exiting\n" TOY_CC_RESET); + exit(-1); + } + + //dummy return + return TOY_VALUE_FROM_NULL(); +} + void Toy_freeValue(Toy_Value value) { switch(value.type) { case TOY_VALUE_NULL: diff --git a/source/toy_value.h b/source/toy_value.h index a4b0851..a2bf5a7 100644 --- a/source/toy_value.h +++ b/source/toy_value.h @@ -82,6 +82,7 @@ TOY_API Toy_Value Toy_unwrapValue(Toy_Value value); TOY_API unsigned int Toy_hashValue(Toy_Value value); TOY_API Toy_Value Toy_copyValue(Toy_Value value); +TOY_API Toy_Value Toy_deepCopyValue(struct Toy_Bucket** bucketHandle, Toy_Value value); //don't use refcounting TOY_API void Toy_freeValue(Toy_Value value); TOY_API bool Toy_checkValueIsTruthy(Toy_Value value); diff --git a/source/toy_vm.c b/source/toy_vm.c index 6489fbd..9bc426e 100644 --- a/source/toy_vm.c +++ b/source/toy_vm.c @@ -360,6 +360,8 @@ static void processAccess(Toy_VM* vm) { return; } + //URGENT: should I loop functions into the reference system? + //in the event of a certain subset of types, create references instead (these should only exist on the stack) if (TOY_VALUE_IS_REFERENCE(*valuePtr) || TOY_VALUE_IS_ARRAY(*valuePtr) || TOY_VALUE_IS_TABLE(*valuePtr)) { Toy_Value ref = TOY_REFERENCE_FROM_POINTER(valuePtr); @@ -433,8 +435,23 @@ static void processInvoke(Toy_VM* vm) { Toy_declareScope(subVM.scope, name, argValue); } - //run and cleanup - Toy_runVM(&subVM); + //run + unsigned int resultCount = Toy_runVM(&subVM); + + //extract and store any results + if (resultCount > 0) { + Toy_Array* results = Toy_extractResultsFromVM(&vm->literalBucket, &subVM, resultCount); + + for (unsigned int i = 0; i < results->count; i++) { + //NOTE: since the results array is being immediately freed, just push each element without a call to copy + Toy_pushStack(&vm->stack, results->data[i]); + } + + //a bit naughty + free(results); + } + + //cleanup Toy_freeVM(&subVM); } break; @@ -628,6 +645,14 @@ static void processLogical(Toy_VM* vm, Toy_OpcodeType opcode) { } } +static unsigned int processReturn(Toy_VM* vm) { + //the values to be returned are waiting on the stack + unsigned int resultCount = (unsigned int)READ_BYTE(vm); + fixAlignment(vm); + + return resultCount; +} + static void processJump(Toy_VM* vm) { Toy_OpParamJumpType type = READ_BYTE(vm); Toy_OpParamJumpConditional cond = READ_BYTE(vm); @@ -926,7 +951,7 @@ static void processIndex(Toy_VM* vm) { Toy_freeValue(length); } -static void process(Toy_VM* vm) { +static unsigned int process(Toy_VM* vm) { while(true) { //prep by aligning to the 4-byte word fixAlignment(vm); @@ -995,8 +1020,7 @@ static void process(Toy_VM* vm) { //control instructions case TOY_OPCODE_RETURN: - //temp terminator - return; + return processReturn(vm); //the only return statement, which signals the number of values for extraction case TOY_OPCODE_JUMP: processJump(vm); @@ -1074,6 +1098,8 @@ void Toy_initVM(Toy_VM* vm) { vm->literalBucket = Toy_allocateBucket(TOY_BUCKET_IDEAL); vm->scopeBucket = Toy_allocateBucket(TOY_BUCKET_IDEAL); + vm->scopeBucketHandle = NULL; //not used + Toy_resetVM(vm, true); } @@ -1081,10 +1107,10 @@ void Toy_inheritVM(Toy_VM* vm, Toy_VM* parent) { //inherent persistent memory vm->scope = NULL; vm->stack = Toy_allocateStack(); - vm->literalBucket = parent->literalBucket; + vm->literalBucket = Toy_allocateBucket(TOY_BUCKET_IDEAL); vm->scopeBucket = parent->scopeBucket; - //TODO: parent bucket pointers are updated after function calls + vm->scopeBucketHandle = &parent->scopeBucket; //track this to update it later Toy_resetVM(vm, true); } @@ -1108,21 +1134,17 @@ void Toy_bindVM(Toy_VM* vm, Toy_Module* module, bool preserveScope) { } } -void Toy_runVM(Toy_VM* vm) { +unsigned int Toy_runVM(Toy_VM* vm) { if (vm->codeAddr == 0) { //ignore uninitialized VMs or empty modules - return; + return 0; } - //TODO: read params into scope - //prep the program counter for execution vm->programCounter = vm->codeAddr; //begin - process(vm); - - //TODO: add return value extraction + return process(vm); } void Toy_freeVM(Toy_VM* vm) { @@ -1131,5 +1153,28 @@ void Toy_freeVM(Toy_VM* vm) { //clear the persistent memory Toy_freeStack(vm->stack); Toy_freeBucket(&vm->literalBucket); - Toy_freeBucket(&vm->scopeBucket); + + if (vm->scopeBucketHandle != NULL) { + *(vm->scopeBucketHandle) = vm->scopeBucket; //re-adjust the parent's scopeBucket pointer, in case it was expanded + } + else { + Toy_freeBucket(&vm->scopeBucket); + } +} + +Toy_Array* Toy_extractResultsFromVM(Toy_Bucket** bucketHandle, Toy_VM* subVM, unsigned int resultCount) { + if (subVM->stack->count < resultCount) { + fprintf(stderr, TOY_CC_ERROR "ERROR: Too many results requested from VM, exiting\n" TOY_CC_RESET); + exit(-1); + } + + Toy_Array* results = Toy_resizeArray(NULL, resultCount); + + const unsigned int offset = subVM->stack->count - resultCount; //first element to extract + + for (/* EMPTY */; results->count < resultCount; results->count++) { + results->data[results->count] = Toy_deepCopyValue(bucketHandle, subVM->stack->data[offset + results->count]); + } + + return results; } diff --git a/source/toy_vm.h b/source/toy_vm.h index 2e1d6fe..4d612a5 100644 --- a/source/toy_vm.h +++ b/source/toy_vm.h @@ -40,17 +40,19 @@ typedef struct Toy_VM { //easy access to memory Toy_Bucket* literalBucket; //stores the value literals (strings, functions, etc.) - Toy_Bucket* scopeBucket; //stores the scope instances TODO: is this separation needed? + Toy_Bucket* scopeBucket; //stores the scope instances + Toy_Bucket** scopeBucketHandle; //for reusing the scope bucket to save on alloc/free } Toy_VM; 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 memory +TOY_API void Toy_inheritVM(Toy_VM* vm, Toy_VM* parent); //inherits scope bucket TOY_API void Toy_bindVM(Toy_VM* vm, Toy_Module* module, bool preserveScope); -TOY_API void Toy_runVM(Toy_VM* vm); - +TOY_API unsigned int Toy_runVM(Toy_VM* vm); TOY_API void Toy_freeVM(Toy_VM* vm); +TOY_API Toy_Array* Toy_extractResultsFromVM(Toy_Bucket** bucketHandle, Toy_VM* subVM, unsigned int resultCount); + //TODO: inject extra data (hook system for external libraries) diff --git a/tests/cases/test_ast.c b/tests/cases/test_ast.c index 9611e21..3ba72cb 100644 --- a/tests/cases/test_ast.c +++ b/tests/cases/test_ast.c @@ -46,6 +46,7 @@ int test_sizeof_ast(void) { TEST_SIZEOF(Toy_AstWhileThen, 12 , 24); TEST_SIZEOF(Toy_AstBreak, 4 , 4); TEST_SIZEOF(Toy_AstContinue, 4 , 4); + TEST_SIZEOF(Toy_AstReturn, 8 , 16); TEST_SIZEOF(Toy_AstPrint, 8 , 16); TEST_SIZEOF(Toy_AstVarDeclare, 12 , 24); TEST_SIZEOF(Toy_AstVarAssign, 16 , 24);