From 93f771dc8dab40747619a667f36a476e997d4eac Mon Sep 17 00:00:00 2001 From: Kayne Ruse Date: Tue, 8 Oct 2024 14:18:10 +1100 Subject: [PATCH] Added interactive loop to the repl, read more Other changes include: * Added Toy_initVM(), to allow Toy_resetVM() to retain memory * Added tests that reset and reuse the same VM --- repl/main.c | 108 +++++++++++++++++++++++++++++++++++++++++- source/toy_vm.c | 15 ++++-- source/toy_vm.h | 10 ++-- tests/cases/test_vm.c | 93 +++++++++++++++++++++++++++++++++++- 4 files changed, 215 insertions(+), 11 deletions(-) diff --git a/repl/main.c b/repl/main.c index 09f8e26..e49aefb 100644 --- a/repl/main.c +++ b/repl/main.c @@ -157,6 +157,85 @@ CmdLine parseCmdLine(int argc, const char* argv[]) { return cmd; } +//repl function +static void errorAndContinueCallback(const char* msg) { + fprintf(stderr, "%s\n", msg); +} + +int repl(const char* name) { + Toy_setErrorCallback(errorAndContinueCallback); + Toy_setAssertFailureCallback(errorAndContinueCallback); + + //vars to use + unsigned int INPUT_BUFFER_SIZE = 4096; + char inputBuffer[INPUT_BUFFER_SIZE]; + memset(inputBuffer, 0, INPUT_BUFFER_SIZE); + + Toy_Bucket* bucket = Toy_allocateBucket(TOY_BUCKET_IDEAL); + + Toy_VM vm; + Toy_initVM(&vm); + + printf("%s> ", name); //shows the terminal prompt + + //read from the terminal + while(fgets(inputBuffer, INPUT_BUFFER_SIZE, stdin)) { + //work around fgets() adding a newline + unsigned int length = strlen(inputBuffer); + if (inputBuffer[length - 1] == '\n') { + inputBuffer[--length] = '\0'; + } + + //end + if (strlen(inputBuffer) == 4 && (strncmp(inputBuffer, "exit", 4) == 0 || strncmp(inputBuffer, "quit", 4) == 0)) { + break; + } + + //parse the input, prep the VM for run + Toy_Lexer lexer; + Toy_bindLexer(&lexer, inputBuffer); + Toy_Parser parser; + Toy_bindParser(&parser, &lexer); + Toy_Ast* ast = Toy_scanParser(&bucket, &parser); //Ast is in the bucket, so it doesn't need to be freed + + //parsing error, retry + if (parser.error) { + printf("%s> ", name); //shows the terminal prompt + continue; + } + + Toy_Bytecode bc = Toy_compileBytecode(ast); + Toy_bindVM(&vm, bc.ptr); + + //run + Toy_runVM(&vm); + + //free the bytecode, and leave the VM ready for the next loop + Toy_freeBytecode(bc); + Toy_resetVM(&vm); + + //count the bucket memory - hang on, this this garbage collection?? + Toy_Bucket* iter = bucket; + int depth = 0; + while (iter->next) { + iter = iter->next; + if (++depth >= 7) { //8 buckets in the chain total, about 8kb allocated + Toy_freeBucket(&bucket); + bucket = Toy_allocateBucket(TOY_BUCKET_IDEAL); + break; + } + } + + printf("%s> ", name); //shows the terminal prompt + } + + //cleanp all memory + Toy_freeVM(&vm); + Toy_freeBucket(&bucket); + + return 0; +} + //callbacks static void printCallback(const char* msg) { fprintf(stdout, "%s\n", msg); @@ -173,6 +252,12 @@ int main(int argc, const char* argv[]) { //TODO: this needs an interactive termi Toy_setErrorCallback(errorAndExitCallback); Toy_setAssertFailureCallback(errorAndExitCallback); + //repl + if (argc == 1) { + return repl(argv[0]); + } + + //if there's args, process them CmdLine cmd = parseCmdLine(argc, argv); if (cmd.error) { @@ -225,12 +310,13 @@ int main(int argc, const char* argv[]) { //TODO: this needs an interactive termi //run the setup Toy_VM vm; + Toy_initVM(&vm); Toy_bindVM(&vm, bc.ptr); //run Toy_runVM(&vm); - //debugging result + //DEBUG: if there's anything left on the stack, print it if (vm.stack->count > 0) { printf("Debug output of the stack after execution\n\ntype\tvalue\n"); for (int i = 0; i < vm.stack->count; i++) { @@ -255,7 +341,24 @@ int main(int argc, const char* argv[]) { //TODO: this needs an interactive termi printf("%f", TOY_VALUE_AS_FLOAT(v)); break; - case TOY_VALUE_STRING: + case TOY_VALUE_STRING: { + Toy_String* str = TOY_VALUE_AS_STRING(v); + + //print based on type + if (str->type == TOY_STRING_NODE) { + char* buffer = Toy_getStringRawBuffer(str); + printf("%s", buffer); + free(buffer); + } + else if (str->type == TOY_STRING_LEAF) { + printf("%s", str->as.leaf.data); + } + else if (str->type == TOY_STRING_NAME) { + printf("%s", str->as.name.data); + } + break; + } + case TOY_VALUE_ARRAY: case TOY_VALUE_DICTIONARY: case TOY_VALUE_FUNCTION: @@ -270,6 +373,7 @@ int main(int argc, const char* argv[]) { //TODO: this needs an interactive termi //cleanup Toy_freeVM(&vm); + Toy_freeBytecode(bc); Toy_freeBucket(&bucket); free(source); } diff --git a/source/toy_vm.c b/source/toy_vm.c index 0478c6c..15cbe9d 100644 --- a/source/toy_vm.c +++ b/source/toy_vm.c @@ -389,6 +389,15 @@ static void process(Toy_VM* vm) { } //exposed functions +void Toy_initVM(Toy_VM* vm) { + //clear the stack, scope and memory + vm->stack = NULL; + //TODO: clear the scope + vm->stringBucket = NULL; + + Toy_resetVM(vm); +} + void Toy_bindVM(Toy_VM* vm, unsigned char* bytecode) { if (bytecode[0] != TOY_VERSION_MAJOR || bytecode[1] > TOY_VERSION_MINOR) { fprintf(stderr, TOY_CC_ERROR "ERROR: Wrong bytecode version found: expected %d.%d.%d found %d.%d.%d, exiting\n" TOY_CC_RESET, TOY_VERSION_MAJOR, TOY_VERSION_MINOR, TOY_VERSION_PATCH, bytecode[0], bytecode[1], bytecode[2]); @@ -417,8 +426,6 @@ void Toy_bindVM(Toy_VM* vm, unsigned char* bytecode) { } void Toy_bindVMToRoutine(Toy_VM* vm, unsigned char* routine) { - Toy_resetVM(vm); - vm->routine = routine; //read the header metadata @@ -471,6 +478,7 @@ void Toy_freeVM(Toy_VM* vm) { //free the bytecode free(vm->bc); + Toy_resetVM(vm); } @@ -493,6 +501,5 @@ void Toy_resetVM(Toy_VM* vm) { vm->routineCounter = 0; - //init the scope & stack - vm->stack = NULL; + //NOTE: stack, scope and memory are not altered by reset } diff --git a/source/toy_vm.h b/source/toy_vm.h index 9af4b18..e84bfbb 100644 --- a/source/toy_vm.h +++ b/source/toy_vm.h @@ -26,21 +26,23 @@ typedef struct Toy_VM { unsigned int routineCounter; - //heap - block-level key/value pairs - //TODO: needs string util for identifiers - //stack - immediate-level values only Toy_Stack* stack; + //heap - block-level key/value pairs + //TODO: needs string util for identifiers + //easy access to memory Toy_Bucket* stringBucket; } Toy_VM; +TOY_API void Toy_initVM(Toy_VM* vm); TOY_API void Toy_bindVM(Toy_VM* vm, unsigned char* bytecode); //process the version data TOY_API void Toy_bindVMToRoutine(Toy_VM* vm, unsigned char* routine); //process the routine only TOY_API void Toy_runVM(Toy_VM* vm); TOY_API void Toy_freeVM(Toy_VM* vm); -TOY_API void Toy_resetVM(Toy_VM* vm); + +TOY_API void Toy_resetVM(Toy_VM* vm); //prepares for another run without deleting stack, scope and memory //TODO: inject extra data diff --git a/tests/cases/test_vm.c b/tests/cases/test_vm.c index b6ddf78..a231506 100644 --- a/tests/cases/test_vm.c +++ b/tests/cases/test_vm.c @@ -11,7 +11,7 @@ #include //utils -Toy_Bytecode makeBytecodeFromSource(Toy_Bucket** bucketHandle, const char* source) { +Toy_Bytecode makeBytecodeFromSource(Toy_Bucket** bucketHandle, const char* source) { //did I forget this? Toy_Lexer lexer; Toy_bindLexer(&lexer, source); @@ -43,6 +43,7 @@ int test_setup_and_teardown(Toy_Bucket** bucketHandle) { //run the setup Toy_VM vm; + Toy_initVM(&vm); Toy_bindVM(&vm, bc.ptr); //check the header size @@ -93,6 +94,7 @@ int test_simple_execution(Toy_Bucket** bucketHandle) { //run the setup Toy_VM vm; + Toy_initVM(&vm); Toy_bindVM(&vm, bc.ptr); //run @@ -137,6 +139,7 @@ int test_opcode_not_equal(Toy_Bucket** bucketHandle) { //run the setup Toy_VM vm; + Toy_initVM(&vm); Toy_bindVM(&vm, bc.ptr); //run @@ -189,6 +192,7 @@ int test_keywords(Toy_Bucket** bucketHandle) { Toy_Bytecode bc = Toy_compileBytecode(ast); Toy_VM vm; + Toy_initVM(&vm); Toy_bindVM(&vm, bc.ptr); //run @@ -230,6 +234,7 @@ int test_keywords(Toy_Bucket** bucketHandle) { Toy_Bytecode bc = Toy_compileBytecode(ast); Toy_VM vm; + Toy_initVM(&vm); Toy_bindVM(&vm, bc.ptr); //run @@ -271,6 +276,7 @@ int test_keywords(Toy_Bucket** bucketHandle) { Toy_Bytecode bc = Toy_compileBytecode(ast); Toy_VM vm; + Toy_initVM(&vm); Toy_bindVM(&vm, bc.ptr); //run @@ -299,6 +305,81 @@ int test_keywords(Toy_Bucket** bucketHandle) { return 0; } +int test_vm_reuse(Toy_Bucket** bucketHandle) { + //run code in the same vm multiple times + { + Toy_setPrintCallback(callbackUtil); + + Toy_VM vm; + Toy_initVM(&vm); + + //run 1 + Toy_Bytecode bc1 = makeBytecodeFromSource(bucketHandle, "print \"Hello world!\";"); + Toy_bindVM(&vm, bc1.ptr); + Toy_runVM(&vm); + Toy_resetVM(&vm); + + if (callbackUtilReceived == NULL || strcmp(callbackUtilReceived, "Hello world!") != 0) { + fprintf(stderr, TOY_CC_ERROR "ERROR: Unexpected value '%s' found in VM reuse run 1\n" TOY_CC_RESET, callbackUtilReceived != NULL ? callbackUtilReceived : "NULL"); + + //cleanup and return + free(callbackUtilReceived); + callbackUtilReceived = NULL; + Toy_freeBytecode(bc1); + Toy_freeVM(&vm); + Toy_resetPrintCallback(); + return -1; + } + Toy_freeBytecode(bc1); + + //run 2 + Toy_Bytecode bc2 = makeBytecodeFromSource(bucketHandle, "print \"Hello world!\";"); + Toy_bindVM(&vm, bc2.ptr); + Toy_runVM(&vm); + Toy_resetVM(&vm); + + if (callbackUtilReceived == NULL || strcmp(callbackUtilReceived, "Hello world!") != 0) { + fprintf(stderr, TOY_CC_ERROR "ERROR: Unexpected value '%s' found in VM reuse run 2\n" TOY_CC_RESET, callbackUtilReceived != NULL ? callbackUtilReceived : "NULL"); + + //cleanup and return + free(callbackUtilReceived); + callbackUtilReceived = NULL; + Toy_freeBytecode(bc2); + Toy_freeVM(&vm); + Toy_resetPrintCallback(); + return -1; + } + Toy_freeBytecode(bc2); + + //run 3 + Toy_Bytecode bc3 = makeBytecodeFromSource(bucketHandle, "print \"Hello world!\";"); + Toy_bindVM(&vm, bc3.ptr); + Toy_runVM(&vm); + Toy_resetVM(&vm); + + if (callbackUtilReceived == NULL || strcmp(callbackUtilReceived, "Hello world!") != 0) { + fprintf(stderr, TOY_CC_ERROR "ERROR: Unexpected value '%s' found in VM reuse run 3\n" TOY_CC_RESET, callbackUtilReceived != NULL ? callbackUtilReceived : "NULL"); + + //cleanup and return + free(callbackUtilReceived); + callbackUtilReceived = NULL; + Toy_freeBytecode(bc3); + Toy_freeVM(&vm); + Toy_resetPrintCallback(); + return -1; + } + Toy_freeBytecode(bc3); + + //cleanup + Toy_freeVM(&vm); + free(callbackUtilReceived); + callbackUtilReceived = NULL; + Toy_resetPrintCallback(); + } + + return 0; +} + int main() { //run each test set, returning the total errors given int total = 0, res = 0; @@ -343,5 +424,15 @@ int main() { total += res; } + { + Toy_Bucket* bucket = Toy_allocateBucket(TOY_BUCKET_IDEAL); + res = test_vm_reuse(&bucket); + Toy_freeBucket(&bucket); + if (res == 0) { + printf(TOY_CC_NOTICE "All good\n" TOY_CC_RESET); + } + total += res; + } + return total; }