From 002651f95df9f61cc67433a813611fcfe50988c5 Mon Sep 17 00:00:00 2001 From: Kayne Ruse Date: Tue, 21 Jan 2025 13:59:04 +1100 Subject: [PATCH] WIP, adjusting architecture, read more The 'source' directory compiles, but the repl and tests are almost untouched so far. There's no guarantee that the code in 'source' is correct, so I'm branching this for a short time, until I'm confident the whole project passes the CI again. I'm adjusting the concepts of routines and bytecode to make them more consistent, and tweaking the VM so it loads from an instance of 'Toy_Module'. * 'Toy_ModuleBuilder' (formally 'Toy_Routine') This is where the AST is compiled, producing a chunk of memory that can be read by the VM. This will eventually operate on individual user-defined functions as well. * 'Toy_ModuleBundle' (formally 'Toy_Bytecode') This collects one or more otherwise unrelated modules into one chunk of memory, stored in sequence. It is also preprended with the version data for Toy's reference implementation: For each byte in the bytecode: 0th: TOY_VERSION_MAJOR 1st: TOY_VERSION_MINOR 2nd: TOY_VERSION_PATCH 3rd: (the number of modules in the bundle) 4th and onwards: TOY_VERSION_BUILD TOY_VERSION_BUILD has always been a null terminated C-string, but from here on, it begins at the word-alignment, and continues until the first word-alignment after the null terminator. As for the 3rd byte listed, since having more than 256 modules in one bundle seems unlikely, I'm storing the count here, as it was otherwise unused. This is a bit janky, but it works for now. * 'Toy_Module' This new structure represents a single complete unit of operation, such as a single source file, or a user-defined function. It is divided into three main sections, with various sub-sections. HEADER (all members are unsigned ints): total module size in bytes jumps count param count data count subs count code addr jumps addr (if jumps count > 0) param addr (if param count > 0) data addr (if data count > 0) subs addr (if subs count > 0) BODY: DATA: jumps table uint array, pointing to addresses in 'data' or 'subs' param table uint array, pointing to addresses in 'data' data heterogeneous data, including strings subs an array of modules, using recursive logic The reference implementation as a whole uses a lot of recursion, so this makes sense. The goal of this rework is so 'Toy_Module' can be added as a member of 'Toy_Value', as a simple and logical way to handle functions. I'll probably use the union pattern, similarly to Toy_String, so functions can be written in C and Toy, and used without needing to worry which is which. --- .notes/README.md | 2 + .notes/bytecode-format.txt | 13 +- .notes/functions.md | 1 + repl/main.c | 8 +- source/toy.h | 25 - source/toy_bytecode.c | 95 -- source/toy_bytecode.h | 15 - source/toy_common.h | 12 +- source/toy_module.c | 40 + source/toy_module.h | 27 + source/toy_module_builder.c | 1163 +++++++++++++++++ .../{toy_routine.h => toy_module_builder.h} | 32 +- source/toy_module_bundle.c | 168 +++ source/toy_module_bundle.h | 20 + source/toy_routine.c | 1160 ---------------- source/toy_vm.c | 196 ++- source/toy_vm.h | 36 +- 17 files changed, 1554 insertions(+), 1459 deletions(-) create mode 100644 .notes/README.md delete mode 100644 source/toy.h delete mode 100644 source/toy_bytecode.c delete mode 100644 source/toy_bytecode.h create mode 100644 source/toy_module.c create mode 100644 source/toy_module.h create mode 100644 source/toy_module_builder.c rename source/{toy_routine.h => toy_module_builder.h} (67%) create mode 100644 source/toy_module_bundle.c create mode 100644 source/toy_module_bundle.h delete mode 100644 source/toy_routine.c diff --git a/.notes/README.md b/.notes/README.md new file mode 100644 index 0000000..644a21e --- /dev/null +++ b/.notes/README.md @@ -0,0 +1,2 @@ +This folder is full of development notes, and are probably out of date. Check the actual docs for the correct info. + diff --git a/.notes/bytecode-format.txt b/.notes/bytecode-format.txt index 6923c64..19b6159 100644 --- a/.notes/bytecode-format.txt +++ b/.notes/bytecode-format.txt @@ -39,27 +39,27 @@ Additional information may be added later, or multiple 'modules' listed sequenti # where 'module' can be omitted if it's local to this module ('identifier' within the symbols is calculated at the module level, it's always unique) .header: - N total size # size of this routine, including all data and subroutines - N .param count # the number of parameter fields expected + N total size # size of this routine, including all data and subroutines N .jumps count # the number of entries in the jump table (should be data count + routine count) + N .param count # the number of parameter fields expected N .data count # the number of data fields expected N .routine count # the number of routines present - .param start # absolute addess of .param; omitted if not needed .code start # absolute address of .code; mandatory + .param start # absolute addess of .param; omitted if not needed .datatable start # absolute address of .datatable; omitted if not needed .data start # absolute address of .data; omitted if not needed .routine start # absolute address of .routine; omitted if not needed # additional metadata fields can be added later -.param: - # a list of symbols to be used as keys in the environment - .code: # instructions read and 'executed' by the interpreter READ 0 LOAD 0 ASSERT +.param: + # a list of symbols to be used as keys in the environment + .jumptable: # a 'symbol -> pointer' jumptable for quickly looking up values in .data and .routines 0 -> {string, 0x00} @@ -71,4 +71,3 @@ Additional information may be added later, or multiple 'modules' listed sequenti .routines: # inner routines, each of which conforms to this spec - diff --git a/.notes/functions.md b/.notes/functions.md index 7dca0cb..a89a7cc 100644 --- a/.notes/functions.md +++ b/.notes/functions.md @@ -80,3 +80,4 @@ API: # Notes * Scopes, buckets, strings, etc. will persist until the root VM is cleared +* The parameters are... \ No newline at end of file diff --git a/repl/main.c b/repl/main.c index b14140b..011234c 100644 --- a/repl/main.c +++ b/repl/main.c @@ -325,7 +325,7 @@ int repl(const char* filepath) { continue; } - Toy_Bytecode bc = Toy_compileBytecode(ast); + Toy_ModuleBundle bc = Toy_compileModuleBundle(ast); Toy_bindVM(&vm, &bc); //run @@ -333,7 +333,7 @@ int repl(const char* filepath) { //free the bytecode, and leave the VM ready for the next loop Toy_resetVM(&vm); - Toy_freeBytecode(bc); + Toy_freeModuleBundle(bc); printf("%s> ", prompt); //shows the terminal prompt } @@ -473,7 +473,7 @@ int main(int argc, const char* argv[]) { Toy_Bucket* bucket = Toy_allocateBucket(TOY_BUCKET_IDEAL); Toy_Ast* ast = Toy_scanParser(&bucket, &parser); - Toy_Bytecode bc = Toy_compileBytecode(ast); + Toy_ModuleBundle bc = Toy_compileModuleBundle(ast); //run the setup Toy_VM vm; @@ -491,7 +491,7 @@ int main(int argc, const char* argv[]) { //cleanup Toy_freeVM(&vm); - Toy_freeBytecode(bc); + Toy_freeModuleBundle(bc); Toy_freeBucket(&bucket); free(source); } diff --git a/source/toy.h b/source/toy.h deleted file mode 100644 index b98b89c..0000000 --- a/source/toy.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -//general utilities -#include "toy_common.h" -#include "toy_console_colors.h" -#include "toy_print.h" - -//basic structures -#include "toy_bucket.h" -#include "toy_string.h" -#include "toy_value.h" -#include "toy_array.h" -#include "toy_stack.h" -#include "toy_table.h" - -//IR structures and other components -#include "toy_ast.h" -#include "toy_routine.h" - -//pipeline -#include "toy_lexer.h" -#include "toy_parser.h" -#include "toy_bytecode.h" -#include "toy_vm.h" - diff --git a/source/toy_bytecode.c b/source/toy_bytecode.c deleted file mode 100644 index c689f3b..0000000 --- a/source/toy_bytecode.c +++ /dev/null @@ -1,95 +0,0 @@ -#include "toy_bytecode.h" -#include "toy_console_colors.h" - -#include "toy_routine.h" - -#include -#include -#include - -//utils -static void expand(Toy_Bytecode* bc, unsigned int amount) { - if (bc->count + amount > bc->capacity) { - - while (bc->count + amount > bc->capacity) { //expand as much as needed - bc->capacity = bc->capacity < 8 ? 8 : bc->capacity * 2; - } - - bc->ptr = realloc(bc->ptr, bc->capacity); - - if (bc->ptr == NULL) { - fprintf(stderr, TOY_CC_ERROR "ERROR: Failed to allocate a 'Toy_Bytecode' of %d capacity\n" TOY_CC_RESET, (int)(bc->capacity)); - exit(1); - } - } -} - -static void emitByte(Toy_Bytecode* bc, unsigned char byte) { - expand(bc, 1); - bc->ptr[bc->count++] = byte; -} - -//bytecode -static void writeBytecodeHeader(Toy_Bytecode* bc) { - emitByte(bc, TOY_VERSION_MAJOR); - emitByte(bc, TOY_VERSION_MINOR); - emitByte(bc, TOY_VERSION_PATCH); - - //check strlen for the build string - const char* build = Toy_private_version_build(); - size_t len = strlen(build) + 1; - - //BUGFIX: ensure the end of the header has 4-byte alignment - if (len % 4 != 1) { //1 to fill the 4th byte above - len += 4 - (len % 4) +1; //ceil - } - - expand(bc, len); - memcpy(bc->ptr + bc->count, build, len); - bc->count += len; - - bc->ptr[bc->count] = '\0'; -} - -static void writeBytecodeBody(Toy_Bytecode* bc, Toy_Ast* ast) { - //a 'module' is a routine that runs at the root-level of a file - //since routines can be recursive, this distinction is important - //eventually, the bytecode may support multiple modules packed into one file - void* module = Toy_compileRoutine(ast); - - //don't try writing an empty module - if (module == NULL) { - return; - } - - size_t len = (size_t)(((int*)module)[0]); - - expand(bc, len); - memcpy(bc->ptr + bc->count, module, len); - bc->count += len; - bc->moduleCount++; - - free(module); -} - -//exposed functions -Toy_Bytecode Toy_compileBytecode(Toy_Ast* ast) { - //setup - Toy_Bytecode bc; - - bc.ptr = NULL; - bc.capacity = 0; - bc.count = 0; - - bc.moduleCount = 0; - - //build - writeBytecodeHeader(&bc); - writeBytecodeBody(&bc, ast); //TODO: implement module packing (multiple modules in one package) - - return bc; -} - -void Toy_freeBytecode(Toy_Bytecode bc) { - free(bc.ptr); -} diff --git a/source/toy_bytecode.h b/source/toy_bytecode.h deleted file mode 100644 index 56cda7d..0000000 --- a/source/toy_bytecode.h +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - -#include "toy_common.h" -#include "toy_ast.h" - -typedef struct Toy_Bytecode { - unsigned char* ptr; - unsigned int capacity; - unsigned int count; - - unsigned int moduleCount; -} Toy_Bytecode; - -TOY_API Toy_Bytecode Toy_compileBytecode(Toy_Ast* ast); -TOY_API void Toy_freeBytecode(Toy_Bytecode bc); diff --git a/source/toy_common.h b/source/toy_common.h index a3a08f6..edcd094 100644 --- a/source/toy_common.h +++ b/source/toy_common.h @@ -47,7 +47,7 @@ #define TOY_BITNESS -1 #endif -//bytecode version specifiers, embedded as the header +//version specifiers, embedded as the header #define TOY_VERSION_MAJOR 2 #define TOY_VERSION_MINOR 0 #define TOY_VERSION_PATCH 0 @@ -56,3 +56,13 @@ #define TOY_VERSION_BUILD Toy_private_version_build() TOY_API const char* Toy_private_version_build(void); +/* + +Version validation rules: + + * Under no circumstance, should you ever run code whose major version is different from the interpreter’s major version + * Under no circumstance, should you ever run code whose minor version is above the interpreter’s minor version + * You may, at your own risk, attempt to run code whose patch version is different from the interpreter’s patch version + * You may, at your own risk, attempt to run code whose build version is different from the interpreter’s build version + +*/ diff --git a/source/toy_module.c b/source/toy_module.c new file mode 100644 index 0000000..d330a49 --- /dev/null +++ b/source/toy_module.c @@ -0,0 +1,40 @@ +#include "toy_module.h" +#include "toy_console_colors.h" + +static inline unsigned int readUnsignedInt(unsigned char** handle) { + unsigned int i = *((unsigned int*)(*handle)); + (*handle) += 4; + return i; +} + +Toy_Module Toy_parseModule(unsigned char* ptr) { + Toy_Module module; + + module.scopePtr = NULL; + + module.code = ptr; + + //header + readUnsignedInt(&ptr); + // module.codeCount = readUnsignedInt(&ptr); NOTE: note used + module.jumpsCount = readUnsignedInt(&ptr); + module.paramCount = readUnsignedInt(&ptr); + module.dataCount = readUnsignedInt(&ptr); + module.subsCount = readUnsignedInt(&ptr); + + module.codeAddr = readUnsignedInt(&ptr); + if (module.jumpsCount) { + module.jumpsAddr = readUnsignedInt(&ptr); + } + if (module.paramCount) { + module.paramAddr = readUnsignedInt(&ptr); + } + if (module.dataCount) { + module.dataAddr = readUnsignedInt(&ptr); + } + if (module.subsCount) { + module.subsAddr = readUnsignedInt(&ptr); + } + + return module; +} \ No newline at end of file diff --git a/source/toy_module.h b/source/toy_module.h new file mode 100644 index 0000000..3899644 --- /dev/null +++ b/source/toy_module.h @@ -0,0 +1,27 @@ +#pragma once + +#include "toy_common.h" +#include "toy_scope.h" + +//runtime module info +typedef struct Toy_Module { + //closure support - points to parent scope + Toy_Scope* scopePtr; + + unsigned char* code; + + //extracted metadata + // unsigned int codeCount; //NOTE: not used + unsigned int jumpsCount; + unsigned int paramCount; + unsigned int dataCount; + unsigned int subsCount; + + unsigned int codeAddr; + unsigned int jumpsAddr; + unsigned int paramAddr; + unsigned int dataAddr; + unsigned int subsAddr; +} Toy_Module; + +TOY_API Toy_Module Toy_parseModule(unsigned char* ptr); diff --git a/source/toy_module_builder.c b/source/toy_module_builder.c new file mode 100644 index 0000000..f83261a --- /dev/null +++ b/source/toy_module_builder.c @@ -0,0 +1,1163 @@ +#include "toy_module_builder.h" +#include "toy_console_colors.h" + +#include "toy_opcodes.h" +#include "toy_value.h" +#include "toy_string.h" + +#include +#include +#include + +//misc. utils +static bool checkForChaining(Toy_Ast* ptr) { + //BUGFIX + if (ptr == NULL) { + return false; + } + + if (ptr->type == TOY_AST_VAR_ASSIGN) { + return true; + } + + if (ptr->type == TOY_AST_UNARY) { + if (ptr->unary.flag >= TOY_AST_FLAG_PREFIX_INCREMENT && ptr->unary.flag <= TOY_AST_FLAG_POSTFIX_DECREMENT) { + return true; + } + } + + return false; +} + +//escapes +void* Toy_private_resizeEscapeArray(Toy_private_EscapeArray* ptr, unsigned int capacity) { + //if you're freeing everything, just return + if (capacity == 0) { + free(ptr); + return NULL; + } + + unsigned int originalCapacity = ptr == NULL ? 0 : ptr->capacity; + unsigned int orignalCount = ptr == NULL ? 0 : ptr->count; + + ptr = (Toy_private_EscapeArray*)realloc(ptr, capacity * sizeof(Toy_private_EscapeEntry_t) + sizeof(Toy_private_EscapeArray)); + + if (ptr == NULL) { + fprintf(stderr, TOY_CC_ERROR "ERROR: Failed to resize an escape array within 'Toy_ModuleBuilder' from %d to %d capacity\n" TOY_CC_RESET, (int)originalCapacity, (int)capacity); + exit(-1); + } + + ptr->capacity = capacity; + ptr->count = orignalCount; + + return ptr; +} + +//writing utils +static void expand(unsigned char** handle, unsigned int* capacity, unsigned int* count, unsigned int amount) { + if ((*count) + amount > (*capacity)) { + while ((*count) + amount > (*capacity)) { + (*capacity) = (*capacity) < 8 ? 8 : (*capacity) * 2; + } + (*handle) = realloc((*handle), (*capacity)); + + if ((*handle) == NULL) { + fprintf(stderr, TOY_CC_ERROR "ERROR: Failed to allocate %d space for a part of 'Toy_ModuleBuilder'\n" TOY_CC_RESET, (int)(*capacity)); + exit(1); + } + } +} + +static void emitByte(unsigned char** handle, unsigned int* capacity, unsigned int* count, unsigned char byte) { + expand(handle, capacity, count, 1); + ((unsigned char*)(*handle))[(*count)++] = byte; +} + +static void emitInt(unsigned char** handle, unsigned int* capacity, unsigned int* count, unsigned int bytes) { + char* ptr = (char*)&bytes; + emitByte(handle, capacity, count, *(ptr++)); + emitByte(handle, capacity, count, *(ptr++)); + emitByte(handle, capacity, count, *(ptr++)); + emitByte(handle, capacity, count, *(ptr++)); +} + +static void emitFloat(unsigned char** handle, unsigned int* capacity, unsigned int* count, float bytes) { + char* ptr = (char*)&bytes; + emitByte(handle, capacity, count, *(ptr++)); + emitByte(handle, capacity, count, *(ptr++)); + emitByte(handle, capacity, count, *(ptr++)); + emitByte(handle, capacity, count, *(ptr++)); +} + +//curry writing utils +#define EMIT_BYTE(mb, part, byte) \ + emitByte((&((*mb)->part)), &((*mb)->part##Capacity), &((*mb)->part##Count), byte) +#define EMIT_INT(mb, part, bytes) \ + emitInt((&((*mb)->part)), &((*mb)->part##Capacity), &((*mb)->part##Count), bytes) +#define EMIT_FLOAT(mb, part, bytes) \ + emitFloat((&((*mb)->part)), &((*mb)->part##Capacity), &((*mb)->part##Count), bytes) + +//skip bytes, but return the address +#define SKIP_BYTE(mb, part) (EMIT_BYTE(mb, part, 0), ((*mb)->part##Count - 1)) +#define SKIP_INT(mb, part) (EMIT_INT(mb, part, 0), ((*mb)->part##Count - 4)) + +//overwrite a pre-existing position +#define OVERWRITE_INT(mb, part, addr, bytes) \ + emitInt((&((*mb)->part)), &((*mb)->part##Capacity), &(addr), bytes); + +//simply get the address (always an integer) +#define CURRENT_ADDRESS(mb, part) ((*mb)->part##Count) + +static void emitToJumpTable(Toy_ModuleBuilder** mb, unsigned int startAddr) { + EMIT_INT(mb, code, (*mb)->jumpsCount); //mark the jump index in the code + EMIT_INT(mb, jumps, startAddr); //save address at the jump index +} + +static unsigned int emitString(Toy_ModuleBuilder** mb, Toy_String* str) { + //4-byte alignment + unsigned int length = str->info.length + 1; + if (length % 4 != 0) { + length += 4 - (length % 4); //ceil + } + + //grab the current start address + unsigned int startAddr = (*mb)->dataCount; + + //move the string into the data section + expand((&((*mb)->data)), &((*mb)->dataCapacity), &((*mb)->dataCount), length); + + if (str->info.type == TOY_STRING_NODE) { + char* buffer = Toy_getStringRawBuffer(str); + memcpy((*mb)->data + (*mb)->dataCount, buffer, str->info.length + 1); + free(buffer); + } + else if (str->info.type == TOY_STRING_LEAF) { + memcpy((*mb)->data + (*mb)->dataCount, str->leaf.data, str->info.length + 1); + } + else if (str->info.type == TOY_STRING_NAME) { + memcpy((*mb)->data + (*mb)->dataCount, str->name.data, str->info.length + 1); + } + + (*mb)->dataCount += length; + + //mark the jump position + emitToJumpTable(mb, startAddr); + + return 1; +} + +static unsigned int writeModuleBuilderCode(Toy_ModuleBuilder** mb, Toy_Ast* ast); //forward declare for recursion +static unsigned int writeInstructionAssign(Toy_ModuleBuilder** mb, Toy_AstVarAssign ast, bool chainedAssignment); //forward declare for chaining of var declarations + +static unsigned int writeInstructionValue(Toy_ModuleBuilder** mb, Toy_AstValue ast) { + EMIT_BYTE(mb, code, TOY_OPCODE_READ); + EMIT_BYTE(mb, code, ast.value.type); + + //emit the raw value based on the type + if (TOY_VALUE_IS_NULL(ast.value)) { + //NOTHING - null's type data is enough + + //4-byte alignment + EMIT_BYTE(mb, code, 0); + EMIT_BYTE(mb, code, 0); + } + else if (TOY_VALUE_IS_BOOLEAN(ast.value)) { + EMIT_BYTE(mb, code, TOY_VALUE_AS_BOOLEAN(ast.value)); + + //4-byte alignment + EMIT_BYTE(mb, code, 0); + } + else if (TOY_VALUE_IS_INTEGER(ast.value)) { + //4-byte alignment + EMIT_BYTE(mb, code, 0); + EMIT_BYTE(mb, code, 0); + + EMIT_INT(mb, code, TOY_VALUE_AS_INTEGER(ast.value)); + } + else if (TOY_VALUE_IS_FLOAT(ast.value)) { + //4-byte alignment + EMIT_BYTE(mb, code, 0); + EMIT_BYTE(mb, code, 0); + + EMIT_FLOAT(mb, code, TOY_VALUE_AS_FLOAT(ast.value)); + } + else if (TOY_VALUE_IS_STRING(ast.value)) { + //4-byte alignment + EMIT_BYTE(mb, code, TOY_STRING_LEAF); //normal string + EMIT_BYTE(mb, code, 0); //can't store the length + + return emitString(mb, TOY_VALUE_AS_STRING(ast.value)); + } + else { + fprintf(stderr, TOY_CC_ERROR "ERROR: Invalid AST type found: Unknown value type\n" TOY_CC_RESET); + exit(-1); + } + + return 1; +} + +static unsigned int writeInstructionUnary(Toy_ModuleBuilder** mb, Toy_AstUnary ast) { + unsigned int result = 0; + + if (ast.flag == TOY_AST_FLAG_NEGATE) { + result = writeModuleBuilderCode(mb, ast.child); + + EMIT_BYTE(mb, code, TOY_OPCODE_NEGATE); + + //4-byte alignment + EMIT_BYTE(mb, code, 0); + EMIT_BYTE(mb, code, 0); + EMIT_BYTE(mb, code, 0); + } + + else if (ast.flag == TOY_AST_FLAG_PREFIX_INCREMENT || ast.flag == TOY_AST_FLAG_PREFIX_DECREMENT) { //NOTE: tightly coupled to the parser's logic, and somewhat duplicates ACCESS + //read the var name onto the stack + Toy_String* name = TOY_VALUE_AS_STRING(ast.child->value.value); + + EMIT_BYTE(mb, code, TOY_OPCODE_READ); + EMIT_BYTE(mb, code, TOY_VALUE_STRING); + EMIT_BYTE(mb, code, TOY_STRING_NAME); + EMIT_BYTE(mb, code, name->info.length); //store the length (max 255) + + emitString(mb, name); + + //duplicate the var name, then get the value + EMIT_BYTE(mb, code,TOY_OPCODE_DUPLICATE); + EMIT_BYTE(mb, code, TOY_OPCODE_ACCESS); //squeezed + EMIT_BYTE(mb, code, 0); + EMIT_BYTE(mb, code, 0); + + //read the integer '1' + EMIT_BYTE(mb, code, TOY_OPCODE_READ); + EMIT_BYTE(mb, code, TOY_VALUE_INTEGER); + EMIT_BYTE(mb, code, 0); + EMIT_BYTE(mb, code, 0); + + EMIT_INT(mb, code, 1); + + //add (or subtract) the two values, then assign (pops the second duplicate, and leaves value on the stack) + EMIT_BYTE(mb, code, ast.flag == TOY_AST_FLAG_PREFIX_INCREMENT ? TOY_OPCODE_ADD : TOY_OPCODE_SUBTRACT); + EMIT_BYTE(mb, code,TOY_OPCODE_ASSIGN); //squeezed + EMIT_BYTE(mb, code,1); + EMIT_BYTE(mb, code,0); + + //leaves one value on the stack + result = 1; + } + + else if (ast.flag == TOY_AST_FLAG_POSTFIX_INCREMENT || ast.flag == TOY_AST_FLAG_POSTFIX_DECREMENT) { //NOTE: ditto + //read the var name onto the stack + Toy_String* name = TOY_VALUE_AS_STRING(ast.child->value.value); + + EMIT_BYTE(mb, code, TOY_OPCODE_READ); + EMIT_BYTE(mb, code, TOY_VALUE_STRING); + EMIT_BYTE(mb, code, TOY_STRING_NAME); + EMIT_BYTE(mb, code, name->info.length); //store the length (max 255) + + emitString(mb, name); + + //access the value (postfix++ and postfix--) + EMIT_BYTE(mb, code, TOY_OPCODE_ACCESS); + EMIT_BYTE(mb, code,0); + EMIT_BYTE(mb, code,0); + EMIT_BYTE(mb, code,0); + + //read the var name onto the stack (again) + name = TOY_VALUE_AS_STRING(ast.child->value.value); + + EMIT_BYTE(mb, code, TOY_OPCODE_READ); + EMIT_BYTE(mb, code, TOY_VALUE_STRING); + EMIT_BYTE(mb, code, TOY_STRING_NAME); + EMIT_BYTE(mb, code, name->info.length); //store the length (max 255) + + emitString(mb, name); + + //duplicate the var name, then get the value + EMIT_BYTE(mb, code,TOY_OPCODE_DUPLICATE); + EMIT_BYTE(mb, code, TOY_OPCODE_ACCESS); //squeezed + EMIT_BYTE(mb, code,0); + EMIT_BYTE(mb, code,0); + + //read the integer '1' + EMIT_BYTE(mb, code, TOY_OPCODE_READ); + EMIT_BYTE(mb, code, TOY_VALUE_INTEGER); + EMIT_BYTE(mb, code, 0); + EMIT_BYTE(mb, code, 0); + + EMIT_INT(mb, code, 1); + + //add (or subtract) the two values, then assign (pops the second duplicate) + EMIT_BYTE(mb, code, ast.flag == TOY_AST_FLAG_POSTFIX_INCREMENT ? TOY_OPCODE_ADD : TOY_OPCODE_SUBTRACT); + EMIT_BYTE(mb, code,TOY_OPCODE_ASSIGN); //squeezed + EMIT_BYTE(mb, code,0); + EMIT_BYTE(mb, code,0); + + //leaves one value on the stack + result = 1; + } + + else { + fprintf(stderr, TOY_CC_ERROR "ERROR: Invalid AST unary flag found\n" TOY_CC_RESET); + exit(-1); + } + + return result; +} + +static unsigned int writeInstructionBinary(Toy_ModuleBuilder** mb, Toy_AstBinary ast) { + //left, then right, then the binary's operation + writeModuleBuilderCode(mb, ast.left); + writeModuleBuilderCode(mb, ast.right); + + if (ast.flag == TOY_AST_FLAG_ADD) { + EMIT_BYTE(mb, code,TOY_OPCODE_ADD); + } + else if (ast.flag == TOY_AST_FLAG_SUBTRACT) { + EMIT_BYTE(mb, code,TOY_OPCODE_SUBTRACT); + } + else if (ast.flag == TOY_AST_FLAG_MULTIPLY) { + EMIT_BYTE(mb, code,TOY_OPCODE_MULTIPLY); + } + else if (ast.flag == TOY_AST_FLAG_DIVIDE) { + EMIT_BYTE(mb, code,TOY_OPCODE_DIVIDE); + } + else if (ast.flag == TOY_AST_FLAG_MODULO) { + EMIT_BYTE(mb, code,TOY_OPCODE_MODULO); + } + + else if (ast.flag == TOY_AST_FLAG_CONCAT) { + EMIT_BYTE(mb, code, TOY_OPCODE_CONCAT); + } + else { + fprintf(stderr, TOY_CC_ERROR "ERROR: Invalid AST binary flag found\n" TOY_CC_RESET); + exit(-1); + } + + //4-byte alignment + EMIT_BYTE(mb, code,TOY_OPCODE_PASS); //checked in combined assignments + EMIT_BYTE(mb, code,0); + EMIT_BYTE(mb, code,0); + + return 1; //leaves only 1 value on the stack +} + +static unsigned int writeInstructionBinaryShortCircuit(Toy_ModuleBuilder** mb, Toy_AstBinaryShortCircuit ast) { + //lhs + writeModuleBuilderCode(mb, ast.left); + + //duplicate the top (so the lhs can be 'returned' by this expression, if needed) + EMIT_BYTE(mb, code,TOY_OPCODE_DUPLICATE); + EMIT_BYTE(mb, code, 0); + EMIT_BYTE(mb, code, 0); + EMIT_BYTE(mb, code, 0); + + // && return the first falsy operand, or the last operand + if (ast.flag == TOY_AST_FLAG_AND) { + EMIT_BYTE(mb, code, TOY_OPCODE_JUMP); + EMIT_BYTE(mb, code, TOY_OP_PARAM_JUMP_RELATIVE); + EMIT_BYTE(mb, code, TOY_OP_PARAM_JUMP_IF_FALSE); + EMIT_BYTE(mb, code, 0); + } + + // || return the first truthy operand, or the last operand + else if (ast.flag == TOY_AST_FLAG_OR) { + EMIT_BYTE(mb, code, TOY_OPCODE_JUMP); + EMIT_BYTE(mb, code, TOY_OP_PARAM_JUMP_RELATIVE); + EMIT_BYTE(mb, code, TOY_OP_PARAM_JUMP_IF_TRUE); + EMIT_BYTE(mb, code, 0); + } + + else { + fprintf(stderr, TOY_CC_ERROR "ERROR: Invalid AST binary short circuit flag found\n" TOY_CC_RESET); + exit(-1); + } + + //parameter address + unsigned int paramAddr = SKIP_INT(mb, code); //parameter to be written later + + //if the lhs value isn't needed, pop it + EMIT_BYTE(mb, code,TOY_OPCODE_ELIMINATE); + EMIT_BYTE(mb, code, 0); + EMIT_BYTE(mb, code, 0); + EMIT_BYTE(mb, code, 0); + + //rhs + writeModuleBuilderCode(mb, ast.right); + + //set the parameter + OVERWRITE_INT(mb, code, paramAddr, CURRENT_ADDRESS(mb, code) - (paramAddr + 4)); + + return 1; //leaves only 1 value on the stack +} + +static unsigned int writeInstructionCompare(Toy_ModuleBuilder** mb, Toy_AstCompare ast) { + //left, then right, then the compare's operation + writeModuleBuilderCode(mb, ast.left); + writeModuleBuilderCode(mb, ast.right); + + if (ast.flag == TOY_AST_FLAG_COMPARE_EQUAL) { + EMIT_BYTE(mb, code,TOY_OPCODE_COMPARE_EQUAL); + } + else if (ast.flag == TOY_AST_FLAG_COMPARE_NOT) { + EMIT_BYTE(mb, code,TOY_OPCODE_COMPARE_EQUAL); + EMIT_BYTE(mb, code,TOY_OPCODE_NEGATE); //squeezed + EMIT_BYTE(mb, code,0); + EMIT_BYTE(mb, code,0); + + return 1; + } + else if (ast.flag == TOY_AST_FLAG_COMPARE_LESS) { + EMIT_BYTE(mb, code,TOY_OPCODE_COMPARE_LESS); + } + else if (ast.flag == TOY_AST_FLAG_COMPARE_LESS_EQUAL) { + EMIT_BYTE(mb, code,TOY_OPCODE_COMPARE_LESS_EQUAL); + } + else if (ast.flag == TOY_AST_FLAG_COMPARE_GREATER) { + EMIT_BYTE(mb, code,TOY_OPCODE_COMPARE_GREATER); + } + else if (ast.flag == TOY_AST_FLAG_COMPARE_GREATER_EQUAL) { + EMIT_BYTE(mb, code,TOY_OPCODE_COMPARE_GREATER_EQUAL); + } + + else { + fprintf(stderr, TOY_CC_ERROR "ERROR: Invalid AST compare flag found\n" TOY_CC_RESET); + exit(-1); + } + + //4-byte alignment (covers most cases) + EMIT_BYTE(mb, code,0); + EMIT_BYTE(mb, code,0); + EMIT_BYTE(mb, code,0); + + return 1; //leaves only 1 value on the stack +} + +static unsigned int writeInstructionGroup(Toy_ModuleBuilder** mb, Toy_AstGroup ast) { + //not certain what this leaves + return writeModuleBuilderCode(mb, ast.child); +} + +static unsigned int writeInstructionCompound(Toy_ModuleBuilder** mb, Toy_AstCompound ast) { + unsigned int result = writeModuleBuilderCode(mb, ast.child); + + if (ast.flag == TOY_AST_FLAG_COMPOUND_ARRAY) { + //signal how many values to read in as array elements + EMIT_BYTE(mb, code, TOY_OPCODE_READ); + EMIT_BYTE(mb, code, TOY_VALUE_ARRAY); + + //4-byte alignment + EMIT_BYTE(mb, code,0); + EMIT_BYTE(mb, code,0); + + //how many elements + EMIT_INT(mb, code, result); + + return 1; //leaves only 1 value on the stack + } + if (ast.flag == TOY_AST_FLAG_COMPOUND_TABLE) { + //signal how many values to read in as table elements + EMIT_BYTE(mb, code, TOY_OPCODE_READ); + EMIT_BYTE(mb, code, TOY_VALUE_TABLE); + + //4-byte alignment + EMIT_BYTE(mb, code,0); + EMIT_BYTE(mb, code,0); + + //how many elements + EMIT_INT(mb, code, result); + + return 1; //leaves only 1 value on the stack + } + else { + fprintf(stderr, TOY_CC_ERROR "ERROR: Invalid AST compound flag found\n" TOY_CC_RESET); + exit(-1); + return 0; + } +} + +static unsigned int writeInstructionAggregate(Toy_ModuleBuilder** mb, Toy_AstAggregate ast) { + unsigned int result = 0; + + //left, then right + result += writeModuleBuilderCode(mb, ast.left); + result += writeModuleBuilderCode(mb, ast.right); + + if (ast.flag == TOY_AST_FLAG_COLLECTION) { + //collections are handled above + return result; + } + else if (ast.flag == TOY_AST_FLAG_PAIR) { + //pairs are handled above + return result; + } + else if (ast.flag == TOY_AST_FLAG_INDEX) { + //value[index, length] + EMIT_BYTE(mb, code, TOY_OPCODE_INDEX); + EMIT_BYTE(mb, code, result); + + //4-byte alignment + EMIT_BYTE(mb, code,0); + EMIT_BYTE(mb, code,0); + + return 1; //leaves only 1 value on the stack + } + else { + fprintf(stderr, TOY_CC_ERROR "ERROR: Invalid AST aggregate flag found\n" TOY_CC_RESET); + exit(-1); + return 0; + } +} + +static unsigned int writeInstructionAssert(Toy_ModuleBuilder** mb, Toy_AstAssert ast) { + //the thing to print + writeModuleBuilderCode(mb, ast.child); + writeModuleBuilderCode(mb, ast.message); + + //output the print opcode + EMIT_BYTE(mb, code, TOY_OPCODE_ASSERT); + + //4-byte alignment + EMIT_BYTE(mb, code, ast.message != NULL ? 2 : 1); //arg count + EMIT_BYTE(mb, code,0); + EMIT_BYTE(mb, code,0); + + return 0; +} + +static unsigned int writeInstructionIfThenElse(Toy_ModuleBuilder** mb, Toy_AstIfThenElse ast) { + //cond-branch + writeModuleBuilderCode(mb, ast.condBranch); + + //emit the jump word (opcode, type, condition, padding) + EMIT_BYTE(mb, code, TOY_OPCODE_JUMP); + EMIT_BYTE(mb, code, TOY_OP_PARAM_JUMP_RELATIVE); + EMIT_BYTE(mb, code, TOY_OP_PARAM_JUMP_IF_FALSE); + EMIT_BYTE(mb, code, 0); + + unsigned int thenParamAddr = SKIP_INT(mb, code); //parameter to be written later + + //emit then-branch + writeModuleBuilderCode(mb, ast.thenBranch); + + if (ast.elseBranch != NULL) { + //emit the jump-to-end (opcode, type, condition, padding) + EMIT_BYTE(mb, code, TOY_OPCODE_JUMP); + EMIT_BYTE(mb, code, TOY_OP_PARAM_JUMP_RELATIVE); + EMIT_BYTE(mb, code, TOY_OP_PARAM_JUMP_ALWAYS); + EMIT_BYTE(mb, code, 0); + + unsigned int elseParamAddr = SKIP_INT(mb, code); //parameter to be written later + + //specify the starting position for the else branch + OVERWRITE_INT(mb, code, thenParamAddr, CURRENT_ADDRESS(mb, code) - (thenParamAddr + 4)); + + //emit the else branch + writeModuleBuilderCode(mb, ast.elseBranch); + + //specify the ending position for the else branch + OVERWRITE_INT(mb, code, elseParamAddr, CURRENT_ADDRESS(mb, code) - (elseParamAddr + 4)); + } + + else { + //without an else branch, set the jump destination and move on + OVERWRITE_INT(mb, code, thenParamAddr, CURRENT_ADDRESS(mb, code) - (thenParamAddr + 4)); + } + + return 0; +} + +static unsigned int writeInstructionWhileThen(Toy_ModuleBuilder** mb, Toy_AstWhileThen ast) { + //begin + unsigned int beginAddr = CURRENT_ADDRESS(mb, code); + + //cond-branch + writeModuleBuilderCode(mb, ast.condBranch); + + //emit the jump word (opcode, type, condition, padding) + EMIT_BYTE(mb, code, TOY_OPCODE_JUMP); + EMIT_BYTE(mb, code, TOY_OP_PARAM_JUMP_RELATIVE); + EMIT_BYTE(mb, code, TOY_OP_PARAM_JUMP_IF_FALSE); + EMIT_BYTE(mb, code, 0); + + unsigned int paramAddr = SKIP_INT(mb, code); //parameter to be written later + + //emit then-branch + writeModuleBuilderCode(mb, ast.thenBranch); + + //jump to begin to repeat the conditional test + EMIT_BYTE(mb, code, TOY_OPCODE_JUMP); + EMIT_BYTE(mb, code, TOY_OP_PARAM_JUMP_RELATIVE); + EMIT_BYTE(mb, code, TOY_OP_PARAM_JUMP_ALWAYS); + EMIT_BYTE(mb, code, 0); + + EMIT_INT(mb, code, beginAddr - (CURRENT_ADDRESS(mb, code) + 4)); //this sets a negative value + + //set the exit parameter for the cond + OVERWRITE_INT(mb, code, paramAddr, CURRENT_ADDRESS(mb, code) - (paramAddr + 4)); + + //set the break & continue data + while ((*mb)->breakEscapes->count > 0) { + //extract + unsigned int addr = (*mb)->breakEscapes->data[(*mb)->breakEscapes->count - 1].addr; + unsigned int depth = (*mb)->breakEscapes->data[(*mb)->breakEscapes->count - 1].depth; + + unsigned int diff = depth - (*mb)->currentScopeDepth; + + OVERWRITE_INT(mb, code, addr, CURRENT_ADDRESS(mb, code) - (addr + 8)); //tell break to come here AFTER reading the instruction + OVERWRITE_INT(mb, code, addr, diff); + + //tick down + (*mb)->breakEscapes->count--; + } + + while ((*mb)->continueEscapes->count > 0) { + //extract + unsigned int addr = (*mb)->continueEscapes->data[(*mb)->continueEscapes->count - 1].addr; + unsigned int depth = (*mb)->continueEscapes->data[(*mb)->continueEscapes->count - 1].depth; + + unsigned int diff = depth - (*mb)->currentScopeDepth; + + OVERWRITE_INT(mb, code, addr, CURRENT_ADDRESS(mb, code) - (addr + 8)); //tell continue to return to the start AFTER reading the instruction + OVERWRITE_INT(mb, code, addr, diff); + + //tick down + (*mb)->continueEscapes->count--; + } + + return 0; +} + +static unsigned int writeInstructionBreak(Toy_ModuleBuilder** mb, Toy_AstBreak ast) { + //unused + (void)ast; + + //escapes are always relative + EMIT_BYTE(mb, code, TOY_OPCODE_ESCAPE); + EMIT_BYTE(mb, code, 0); + EMIT_BYTE(mb, code, 0); + EMIT_BYTE(mb, code, 0); + + unsigned int addr = SKIP_INT(mb, code); + (void)SKIP_INT(mb, code); //empty space for depth + + //expand the escape array if needed + if ((*mb)->breakEscapes->capacity <= (*mb)->breakEscapes->count) { + (*mb)->breakEscapes = Toy_private_resizeEscapeArray((*mb)->breakEscapes, (*mb)->breakEscapes->capacity * TOY_ESCAPE_EXPANSION_RATE); + } + + //store for later + (*mb)->breakEscapes->data[(*mb)->breakEscapes->count++] = (Toy_private_EscapeEntry_t){ .addr = addr, .depth = (*mb)->currentScopeDepth }; + + return 0; +} + +static unsigned int writeInstructionContinue(Toy_ModuleBuilder** mb, Toy_AstContinue ast) { + //unused + (void)ast; + + //escapes are always relative + EMIT_BYTE(mb, code, TOY_OPCODE_ESCAPE); + EMIT_BYTE(mb, code, 0); + EMIT_BYTE(mb, code, 0); + EMIT_BYTE(mb, code, 0); + + unsigned int addr = SKIP_INT(mb, code); + (void)SKIP_INT(mb, code); //empty space for depth + + //expand the escape array if needed + if ((*mb)->continueEscapes->capacity <= (*mb)->continueEscapes->count) { + (*mb)->continueEscapes = Toy_private_resizeEscapeArray((*mb)->continueEscapes, (*mb)->continueEscapes->capacity * TOY_ESCAPE_EXPANSION_RATE); + } + + //store for later + (*mb)->continueEscapes->data[(*mb)->continueEscapes->count++] = (Toy_private_EscapeEntry_t){ .addr = addr, .depth = (*mb)->currentScopeDepth }; + + return 0; +} + +static unsigned int writeInstructionPrint(Toy_ModuleBuilder** mb, Toy_AstPrint ast) { + //the thing to print + writeModuleBuilderCode(mb, ast.child); + + //output the print opcode + EMIT_BYTE(mb, code,TOY_OPCODE_PRINT); + + //4-byte alignment + EMIT_BYTE(mb, code,0); + EMIT_BYTE(mb, code,0); + EMIT_BYTE(mb, code,0); + + return 0; +} + +static unsigned int writeInstructionVarDeclare(Toy_ModuleBuilder** mb, Toy_AstVarDeclare ast) { + //if we're dealing with chained assignments, hijack the next assignment with 'chainedAssignment' set to true + if (checkForChaining(ast.expr)) { + writeInstructionAssign(mb, ast.expr->varAssign, true); + } + else { + writeModuleBuilderCode(mb, ast.expr); //default value + } + + //delcare with the given name string + EMIT_BYTE(mb, code, TOY_OPCODE_DECLARE); + EMIT_BYTE(mb, code, Toy_getNameStringVarType(ast.name)); + EMIT_BYTE(mb, code, ast.name->info.length); //quick optimisation to skip a 'strlen()' call + EMIT_BYTE(mb, code, Toy_getNameStringVarConstant(ast.name) ? 1 : 0); //check for constness + + emitString(mb, ast.name); + + return 0; +} + +static unsigned int writeInstructionAssign(Toy_ModuleBuilder** mb, Toy_AstVarAssign ast, bool chainedAssignment) { + unsigned int result = 0; + + //target is a name string + if (ast.target->type == TOY_AST_VALUE && TOY_VALUE_IS_STRING(ast.target->value.value) && TOY_VALUE_AS_STRING(ast.target->value.value)->info.type == TOY_STRING_NAME) { + //name string + Toy_String* target = TOY_VALUE_AS_STRING(ast.target->value.value); + + //emit the name string + EMIT_BYTE(mb, code, TOY_OPCODE_READ); + EMIT_BYTE(mb, code, TOY_VALUE_STRING); + EMIT_BYTE(mb, code, TOY_STRING_NAME); + EMIT_BYTE(mb, code, target->info.length); //store the length (max 255) + + emitString(mb, target); + } + + //target is an indexing of some compound value + else if (ast.target->type == TOY_AST_AGGREGATE && ast.target->aggregate.flag == TOY_AST_FLAG_INDEX) { + writeModuleBuilderCode(mb, ast.target->aggregate.left); //any deeper indexing will just work, using reference values + writeModuleBuilderCode(mb, ast.target->aggregate.right); //key + + //if we're dealing with chained assignments, hijack the next assignment with 'chainedAssignment' set to true + if (checkForChaining(ast.expr)) { + result += writeInstructionAssign(mb, ast.expr->varAssign, true); + } + else { + result += writeModuleBuilderCode(mb, ast.expr); //default value + } + + EMIT_BYTE(mb, code, TOY_OPCODE_ASSIGN_COMPOUND); //uses the top three values on the stack + EMIT_BYTE(mb, code, chainedAssignment); + EMIT_BYTE(mb, code,0); + EMIT_BYTE(mb, code,0); + + return result + (chainedAssignment ? 1 : 0); + } + + else { + //unknown target + fprintf(stderr, TOY_CC_ERROR "COMPILER ERROR: Invalid AST type found: Malformed assignment target\n" TOY_CC_RESET); + (*mb)->panic = true; + return 0; + } + + //determine RHS, include duplication if needed + if (ast.flag == TOY_AST_FLAG_ASSIGN) { + //if we're dealing with chained assignments, hijack the next assignment with 'chainedAssignment' set to true + if (checkForChaining(ast.expr)) { + result += writeInstructionAssign(mb, ast.expr->varAssign, true); + } + else { + result += writeModuleBuilderCode(mb, ast.expr); //default value + } + + EMIT_BYTE(mb, code, TOY_OPCODE_ASSIGN); + EMIT_BYTE(mb, code, chainedAssignment); + EMIT_BYTE(mb, code,0); + EMIT_BYTE(mb, code,0); + } + else if (ast.flag == TOY_AST_FLAG_ADD_ASSIGN) { + EMIT_BYTE(mb, code,TOY_OPCODE_DUPLICATE); + EMIT_BYTE(mb, code,TOY_OPCODE_ACCESS); //squeezed + EMIT_BYTE(mb, code,0); + EMIT_BYTE(mb, code,0); + + //if we're dealing with chained assignments, hijack the next assignment with 'chainedAssignment' set to true + if (checkForChaining(ast.expr)) { + result += writeInstructionAssign(mb, ast.expr->varAssign, true); + } + else { + result += writeModuleBuilderCode(mb, ast.expr); //default value + } + + EMIT_BYTE(mb, code,TOY_OPCODE_ADD); + EMIT_BYTE(mb, code,TOY_OPCODE_ASSIGN); //squeezed + EMIT_BYTE(mb, code, chainedAssignment); + EMIT_BYTE(mb, code,0); + } + else if (ast.flag == TOY_AST_FLAG_SUBTRACT_ASSIGN) { + EMIT_BYTE(mb, code,TOY_OPCODE_DUPLICATE); + EMIT_BYTE(mb, code,TOY_OPCODE_ACCESS); //squeezed + EMIT_BYTE(mb, code,0); + EMIT_BYTE(mb, code,0); + + //if we're dealing with chained assignments, hijack the next assignment with 'chainedAssignment' set to true + if (checkForChaining(ast.expr)) { + result += writeInstructionAssign(mb, ast.expr->varAssign, true); + } + else { + result += writeModuleBuilderCode(mb, ast.expr); //default value + } + + EMIT_BYTE(mb, code,TOY_OPCODE_SUBTRACT); + EMIT_BYTE(mb, code,TOY_OPCODE_ASSIGN); //squeezed + EMIT_BYTE(mb, code, chainedAssignment); + EMIT_BYTE(mb, code,0); + } + else if (ast.flag == TOY_AST_FLAG_MULTIPLY_ASSIGN) { + EMIT_BYTE(mb, code,TOY_OPCODE_DUPLICATE); + EMIT_BYTE(mb, code,TOY_OPCODE_ACCESS); //squeezed + EMIT_BYTE(mb, code,0); + EMIT_BYTE(mb, code,0); + + //if we're dealing with chained assignments, hijack the next assignment with 'chainedAssignment' set to true + if (checkForChaining(ast.expr)) { + result += writeInstructionAssign(mb, ast.expr->varAssign, true); + } + else { + result += writeModuleBuilderCode(mb, ast.expr); //default value + } + + EMIT_BYTE(mb, code,TOY_OPCODE_MULTIPLY); + EMIT_BYTE(mb, code,TOY_OPCODE_ASSIGN); //squeezed + EMIT_BYTE(mb, code, chainedAssignment); + EMIT_BYTE(mb, code,0); + } + else if (ast.flag == TOY_AST_FLAG_DIVIDE_ASSIGN) { + EMIT_BYTE(mb, code,TOY_OPCODE_DUPLICATE); + EMIT_BYTE(mb, code,TOY_OPCODE_ACCESS); //squeezed + EMIT_BYTE(mb, code,0); + EMIT_BYTE(mb, code,0); + + //if we're dealing with chained assignments, hijack the next assignment with 'chainedAssignment' set to true + if (checkForChaining(ast.expr)) { + result += writeInstructionAssign(mb, ast.expr->varAssign, true); + } + else { + result += writeModuleBuilderCode(mb, ast.expr); //default value + } + + EMIT_BYTE(mb, code,TOY_OPCODE_DIVIDE); + EMIT_BYTE(mb, code,TOY_OPCODE_ASSIGN); //squeezed + EMIT_BYTE(mb, code, chainedAssignment); + EMIT_BYTE(mb, code,0); + } + else if (ast.flag == TOY_AST_FLAG_MODULO_ASSIGN) { + EMIT_BYTE(mb, code,TOY_OPCODE_DUPLICATE); + EMIT_BYTE(mb, code,TOY_OPCODE_ACCESS); //squeezed + EMIT_BYTE(mb, code,0); + EMIT_BYTE(mb, code,0); + + //if we're dealing with chained assignments, hijack the next assignment with 'chainedAssignment' set to true + if (checkForChaining(ast.expr)) { + result += writeInstructionAssign(mb, ast.expr->varAssign, true); + } + else { + result += writeModuleBuilderCode(mb, ast.expr); //default value + } + + EMIT_BYTE(mb, code,TOY_OPCODE_MODULO); + EMIT_BYTE(mb, code,TOY_OPCODE_ASSIGN); //squeezed + EMIT_BYTE(mb, code, chainedAssignment); + EMIT_BYTE(mb, code,0); + } + + else { + fprintf(stderr, TOY_CC_ERROR "ERROR: Invalid AST assign flag found\n" TOY_CC_RESET); + exit(-1); + } + + return result + (chainedAssignment ? 1 : 0); +} + +static unsigned int writeInstructionAccess(Toy_ModuleBuilder** mb, Toy_AstVarAccess ast) { + if (!(ast.child->type == TOY_AST_VALUE && TOY_VALUE_IS_STRING(ast.child->value.value) && TOY_VALUE_AS_STRING(ast.child->value.value)->info.type == TOY_STRING_NAME)) { + fprintf(stderr, TOY_CC_ERROR "COMPILER ERROR: Found a non-name-string in a value node when trying to write access\n" TOY_CC_RESET); + exit(-1); + } + + Toy_String* name = TOY_VALUE_AS_STRING(ast.child->value.value); + + //push the name + EMIT_BYTE(mb, code, TOY_OPCODE_READ); + EMIT_BYTE(mb, code, TOY_VALUE_STRING); + EMIT_BYTE(mb, code, TOY_STRING_NAME); + EMIT_BYTE(mb, code, name->info.length); //store the length (max 255) + + emitString(mb, name); + + //convert name to value + EMIT_BYTE(mb, code, TOY_OPCODE_ACCESS); + EMIT_BYTE(mb, code,0); + EMIT_BYTE(mb, code,0); + EMIT_BYTE(mb, code,0); + + return 1; +} + +//routine structure +// static void writeModuleBuilderParam(Toy_ModuleBuilder* mb) { +// // +// } + +static unsigned int writeModuleBuilderCode(Toy_ModuleBuilder** mb, Toy_Ast* ast) { + if (ast == NULL) { + return 0; + } + + //if an error occured, just exit + if (mb == NULL || (*mb) == NULL || (*mb)->panic) { + return 0; + } + + //NOTE: 'result' is used to in 'writeInstructionAggregate()' + unsigned int result = 0; + + //determine how to write each instruction based on the Ast + switch(ast->type) { + case TOY_AST_BLOCK: + if (ast->block.innerScope) { + EMIT_BYTE(mb, code, TOY_OPCODE_SCOPE_PUSH); + EMIT_BYTE(mb, code, 0); + EMIT_BYTE(mb, code, 0); + EMIT_BYTE(mb, code, 0); + + (*mb)->currentScopeDepth++; + } + + result += writeModuleBuilderCode(mb, ast->block.child); + result += writeModuleBuilderCode(mb, ast->block.next); + + if (ast->block.innerScope) { + EMIT_BYTE(mb, code, TOY_OPCODE_SCOPE_POP); + EMIT_BYTE(mb, code, 0); + EMIT_BYTE(mb, code, 0); + EMIT_BYTE(mb, code, 0); + + (*mb)->currentScopeDepth--; + } + break; + + case TOY_AST_VALUE: + result += writeInstructionValue(mb, ast->value); + break; + + case TOY_AST_UNARY: + result += writeInstructionUnary(mb, ast->unary); + break; + + case TOY_AST_BINARY: + result += writeInstructionBinary(mb, ast->binary); + break; + + case TOY_AST_BINARY_SHORT_CIRCUIT: + result += writeInstructionBinaryShortCircuit(mb, ast->binaryShortCircuit); + break; + + case TOY_AST_COMPARE: + result += writeInstructionCompare(mb, ast->compare); + break; + + case TOY_AST_GROUP: + result += writeInstructionGroup(mb, ast->group); + break; + + case TOY_AST_COMPOUND: + result += writeInstructionCompound(mb, ast->compound); + break; + + case TOY_AST_AGGREGATE: + result += writeInstructionAggregate(mb, ast->aggregate); + break; + + case TOY_AST_ASSERT: + result += writeInstructionAssert(mb, ast->assert); + break; + + case TOY_AST_IF_THEN_ELSE: + result += writeInstructionIfThenElse(mb, ast->ifThenElse); + break; + + case TOY_AST_WHILE_THEN: + result += writeInstructionWhileThen(mb, ast->whileThen); + break; + + case TOY_AST_BREAK: + result += writeInstructionBreak(mb, ast->breakPoint); + break; + + case TOY_AST_CONTINUE: + result += writeInstructionContinue(mb, ast->continuePoint); + break; + + case TOY_AST_PRINT: + result += writeInstructionPrint(mb, ast->print); + break; + + case TOY_AST_VAR_DECLARE: + result += writeInstructionVarDeclare(mb, ast->varDeclare); + break; + + case TOY_AST_VAR_ASSIGN: + result += writeInstructionAssign(mb, ast->varAssign, false); + break; + + case TOY_AST_VAR_ACCESS: + result += writeInstructionAccess(mb, ast->varAccess); + break; + + case TOY_AST_PASS: + //NO-OP + break; + + //meta instructions are disallowed + case TOY_AST_ERROR: + fprintf(stderr, TOY_CC_ERROR "COMPILER ERROR: Invalid AST type found: Unknown 'error'\n" TOY_CC_RESET); + (*mb)->panic = true; + break; + + case TOY_AST_END: + fprintf(stderr, TOY_CC_ERROR "COMPILER ERROR: Invalid AST type found: Unknown 'end'\n" TOY_CC_RESET); + (*mb)->panic = true; + break; + } + + return result; +} + +static void* writeModuleBuilder(Toy_ModuleBuilder* mb, Toy_Ast* ast) { + //code + writeModuleBuilderCode(&mb, ast); + + EMIT_BYTE(&mb, code, TOY_OPCODE_RETURN); //end terminator + EMIT_BYTE(&mb, code, 0); //4-byte alignment + EMIT_BYTE(&mb, code, 0); + EMIT_BYTE(&mb, code, 0); + + //if an error occurred, just exit + if (mb->panic) { + return NULL; + } + + //write the header and combine the parts + unsigned char* buffer = NULL; + unsigned int capacity = 0, count = 0; + int codeAddr = 0; + int jumpsAddr = 0; + // int paramAddr = 0; + int dataAddr = 0; + // int subsAddr = 0; + + emitInt(&buffer, &capacity, &count, 0); //total size (overwritten later) + emitInt(&buffer, &capacity, &count, mb->jumpsCount); //jumps size + emitInt(&buffer, &capacity, &count, mb->paramCount); //param size + emitInt(&buffer, &capacity, &count, mb->dataCount); //data size + emitInt(&buffer, &capacity, &count, mb->subsCount); //routine size + + //generate blank spaces, cache their positions in the *Addr variables for later writes + if (mb->codeCount > 0) { + codeAddr = count; + emitInt(&buffer, &capacity, &count, 0); //code + } + if (mb->jumpsCount > 0) { + jumpsAddr = count; + emitInt(&buffer, &capacity, &count, 0); //jumps + } + if (mb->paramCount > 0) { + // paramAddr = count; + emitInt(&buffer, &capacity, &count, 0); //params + } + if (mb->dataCount > 0) { + dataAddr = count; + emitInt(&buffer, &capacity, &count, 0); //data + } + if (mb->subsCount > 0) { + // subsAddr = count; + emitInt(&buffer, &capacity, &count, 0); //subs + } + + //append various parts to the buffer + if (mb->codeCount > 0) { + expand(&buffer, &capacity, &count, mb->codeCount); + memcpy((buffer + count), mb->code, mb->codeCount); + + *((int*)(buffer + codeAddr)) = count; + count += mb->codeCount; + } + + if (mb->jumpsCount > 0) { + expand(&buffer, &capacity, &count, mb->jumpsCount); + memcpy((buffer + count), mb->jumps, mb->jumpsCount); + + *((int*)(buffer + jumpsAddr)) = count; + count += mb->jumpsCount; + } + + //TODO: param region + + if (mb->dataCount > 0) { + expand(&buffer, &capacity, &count, mb->dataCount); + memcpy((buffer + count), mb->data, mb->dataCount); + + *((int*)(buffer + dataAddr)) = count; + count += mb->dataCount; + } + + //TODO: subs region + + //finally, record the total size within the header, and return the result + ((int*)buffer)[0] = count; + + return buffer; +} + +//exposed functions +void* Toy_compileModuleBuilder(Toy_Ast* ast) { + //setup + Toy_ModuleBuilder builder; + + builder.code = NULL; + builder.codeCapacity = 0; + builder.codeCount = 0; + + builder.jumps = NULL; + builder.jumpsCapacity = 0; + builder.jumpsCount = 0; + + builder.param = NULL; + builder.paramCapacity = 0; + builder.paramCount = 0; + + builder.data = NULL; + builder.dataCapacity = 0; + builder.dataCount = 0; + + builder.subs = NULL; + builder.subsCapacity = 0; + builder.subsCount = 0; + + builder.currentScopeDepth = 0; + builder.breakEscapes = Toy_private_resizeEscapeArray(NULL, TOY_ESCAPE_INITIAL_CAPACITY); + builder.continueEscapes = Toy_private_resizeEscapeArray(NULL, TOY_ESCAPE_INITIAL_CAPACITY); + + builder.panic = false; + + //build + void * buffer = writeModuleBuilder(&builder, ast); + + //cleanup + Toy_private_resizeEscapeArray(builder.breakEscapes, 0); + Toy_private_resizeEscapeArray(builder.continueEscapes, 0); + + free(builder.param); + free(builder.code); + free(builder.jumps); + free(builder.data); + free(builder.subs); + + return buffer; +} diff --git a/source/toy_routine.h b/source/toy_module_builder.h similarity index 67% rename from source/toy_routine.h rename to source/toy_module_builder.h index 34457b0..6c1c486 100644 --- a/source/toy_routine.h +++ b/source/toy_module_builder.h @@ -3,7 +3,7 @@ #include "toy_common.h" #include "toy_ast.h" -//the 'escapes' are lists of data used for processing the 'break' and 'continue' keywords, and can be safely ignored +//the 'escapes' are lists of data used for processing the 'break' and 'continue' keywords typedef struct Toy_private_EscapeEntry_t { unsigned int addr; //the address to write *to* unsigned int depth; //the current depth @@ -26,12 +26,8 @@ typedef struct Toy_private_EscapeArray { TOY_API void* Toy_private_resizeEscapeArray(Toy_private_EscapeArray* ptr, unsigned int capacity); -//internal structure that holds the individual parts of a compiled routine -typedef struct Toy_Routine { - unsigned char* param; //c-string params in sequence (could be moved below the jump table?) - unsigned int paramCapacity; - unsigned int paramCount; - +//structure for holding the module as it is built +typedef struct Toy_ModuleBuilder { unsigned char* code; //the instruction set unsigned int codeCapacity; unsigned int codeCount; @@ -40,23 +36,27 @@ typedef struct Toy_Routine { unsigned int jumpsCapacity; unsigned int jumpsCount; - unsigned char* data; //data for longer stuff + unsigned char* param; //each 'param' is the starting address of a name string within 'data' + unsigned int paramCapacity; + unsigned int paramCount; + + unsigned char* data; //a block of read-only data unsigned int dataCapacity; unsigned int dataCount; - unsigned char* subs; //subroutines, recursively + unsigned char* subs; //submodules, built recursively unsigned int subsCapacity; unsigned int subsCount; + //TODO: duplicate string reuse, see #168 + + //tools for handling the build process unsigned int currentScopeDepth; Toy_private_EscapeArray* breakEscapes; Toy_private_EscapeArray* continueEscapes; - bool panic; //any issues found at this point are compilation errors -} Toy_Routine; + //compilation errors + bool panic; +} Toy_ModuleBuilder; -TOY_API void* Toy_compileRoutine(Toy_Ast* ast); - -//URGENT: Rename routines to ModuleBuilder -//URGENT: Rename bytecode to ModuleBundler -//URGENT: Compiled code is a "module" +TOY_API void* Toy_compileModuleBuilder(Toy_Ast* ast); diff --git a/source/toy_module_bundle.c b/source/toy_module_bundle.c new file mode 100644 index 0000000..525f4a4 --- /dev/null +++ b/source/toy_module_bundle.c @@ -0,0 +1,168 @@ +#include "toy_module_bundle.h" +#include "toy_console_colors.h" + +#include "toy_module_builder.h" + +#include +#include +#include + +//utils +static void expand(Toy_ModuleBundle* bundle, unsigned int amount) { + if (bundle->count + amount > bundle->capacity) { + bundle->capacity = 0; + + while (bundle->count + amount > bundle->capacity) { //expand as much as needed + bundle->capacity >>= 2; + } + + bundle->ptr = realloc(bundle->ptr, bundle->capacity); + + if (bundle->ptr == NULL) { + fprintf(stderr, TOY_CC_ERROR "ERROR: Failed to allocate a 'Toy_ModuleBundle' of %d capacity\n" TOY_CC_RESET, (int)(bundle->capacity)); + exit(1); + } + } +} + +static void emitByte(Toy_ModuleBundle* bundle, unsigned char byte) { + expand(bundle, 1); + bundle->ptr[bundle->count++] = byte; +} + +static void writeModuleBundleHeader(Toy_ModuleBundle* bundle) { + emitByte(bundle, TOY_VERSION_MAJOR); + emitByte(bundle, TOY_VERSION_MINOR); + emitByte(bundle, TOY_VERSION_PATCH); + emitByte(bundle, 0); //module count + + //get the build string + const char* build = Toy_private_version_build(); + size_t len = strlen(build) + 1; //includes null + + //emit the build string + expand(bundle, len); + strncpy((char*)(bundle->ptr + bundle->count), build, len); + bundle->count += len; + + //align the count + bundle->count = (bundle->count + 3) & ~3; +} + +static int validateModuleBundleHeader(Toy_ModuleBundle* bundle) { + if (bundle->ptr[0] != TOY_VERSION_MAJOR || bundle->ptr[1] > TOY_VERSION_MINOR) { + return -1; + } + + if (bundle->ptr[2] != TOY_VERSION_PATCH) { + return 1; + } + + if (strcmp((char*)(bundle->ptr + 4), TOY_VERSION_BUILD) != 0) { + return 2; + } + + return 0; +} + +//exposed functions +void Toy_initModuleBundle(Toy_ModuleBundle* bundle) { + bundle->ptr = NULL; + bundle->capacity = 0; + bundle->count = 0; +} + +void Toy_appendModuleBundle(Toy_ModuleBundle* bundle, Toy_Ast* ast) { + //probably some inefficincies in memory usage here + if (bundle->capacity == 0) { + writeModuleBundleHeader(bundle); //TODO: update the header? + } + + //increment the module count + if (bundle->ptr[3] < 255) { + bundle->ptr[3]++; + } + else { + fprintf(stderr, TOY_CC_ERROR "ERROR: Too many modules in a bundle\n" TOY_CC_RESET); + exit(-1); + } + + void* module = Toy_compileModuleBuilder(ast); + + //don't try writing an empty module + if (module == NULL) { + return; + } + + //write the module to the bundle + size_t len = (size_t)(((int*)module)[0]); + + expand(bundle, len); + memcpy(bundle->ptr + bundle->count, module, len); + bundle->count += len; + + free(module); +} + +void Toy_freeModuleBundle(Toy_ModuleBundle* bundle) { + free(bundle->ptr); + Toy_initModuleBundle(bundle); +} + +void Toy_bindModuleBundle(Toy_ModuleBundle* bundle, unsigned char* ptr, unsigned int size) { + if (bundle == NULL) { + fprintf(stderr, TOY_CC_ERROR "ERROR: Can't bind a NULL bundle\n" TOY_CC_RESET); + exit(-1); + } + + if (bundle->ptr != NULL || bundle->capacity != 0 || bundle->count != 0) { + fprintf(stderr, TOY_CC_ERROR "ERROR: Can't bind a bundle with pre-existing contents\n" TOY_CC_RESET); + exit(-1); + } + + //copy + expand(bundle, size); + + memcpy(bundle->ptr, ptr, size); + bundle->count = size; + + //URGENT: test this + int valid = validateModuleBundleHeader(bundle); + + if (valid < 0) { + fprintf(stderr, TOY_CC_ERROR "ERROR: Wrong version info found in module header: expected %d.%d.%d.%s found %d.%d.%d.%s, exiting\n" TOY_CC_RESET, TOY_VERSION_MAJOR, TOY_VERSION_MINOR, TOY_VERSION_PATCH, TOY_VERSION_BUILD, bundle->ptr[0], bundle->ptr[1], bundle->ptr[2], (char*)(bundle->ptr + 4)); + exit(valid); + } + + if (valid > 0) { + fprintf(stderr, TOY_CC_WARN "WARNING: Wrong version info found in module header: expected %d.%d.%d.%s found %d.%d.%d.%s, continuing\n" TOY_CC_RESET, TOY_VERSION_MAJOR, TOY_VERSION_MINOR, TOY_VERSION_PATCH, TOY_VERSION_BUILD, bundle->ptr[0], bundle->ptr[1], bundle->ptr[2], (char*)(bundle->ptr + 4)); + } +} + +Toy_Module Toy_extractModuleFromBundle(Toy_ModuleBundle* bundle, unsigned char index) { + if (bundle == NULL) { + fprintf(stderr, TOY_CC_ERROR "ERROR: Can't extract from a NULL bundle\n" TOY_CC_RESET); + return (Toy_Module){ 0 }; + } + + if (bundle->ptr == NULL) { + fprintf(stderr, TOY_CC_ERROR "ERROR: Can't extract from an empty bundle\n" TOY_CC_RESET); + return (Toy_Module){ 0 }; + } + + //yes, it's a bit awkward + char* buildPtr = (char*)(bundle->ptr + 4); + int buildLen = strlen(buildPtr); + buildLen = (buildLen + 3) & ~3; + + //first module's start position + unsigned char* moduleHead = bundle->ptr + 4 + buildLen; + + for (unsigned char i = 0; i < index; i++) { + unsigned int size = *((int*)(moduleHead)); + moduleHead += size; + } + + //read in the module + return Toy_parseModule(moduleHead); +} diff --git a/source/toy_module_bundle.h b/source/toy_module_bundle.h new file mode 100644 index 0000000..e1b713f --- /dev/null +++ b/source/toy_module_bundle.h @@ -0,0 +1,20 @@ +#pragma once + +#include "toy_common.h" +#include "toy_ast.h" +#include "toy_module.h" + +typedef struct Toy_ModuleBundle { + unsigned char* ptr; + unsigned int capacity; + unsigned int count; +} Toy_ModuleBundle; + +//create a bundle +TOY_API void Toy_initModuleBundle(Toy_ModuleBundle* bundle); +TOY_API void Toy_appendModuleBundle(Toy_ModuleBundle* bundle, Toy_Ast* ast); +TOY_API void Toy_freeModuleBundle(Toy_ModuleBundle* bundle); + +//load module bundle with external data (makes an internal copy) +TOY_API void Toy_bindModuleBundle(Toy_ModuleBundle* bundle, unsigned char* ptr, unsigned int size); +TOY_API Toy_Module Toy_extractModuleFromBundle(Toy_ModuleBundle* bundle, unsigned char index); diff --git a/source/toy_routine.c b/source/toy_routine.c deleted file mode 100644 index 757cb20..0000000 --- a/source/toy_routine.c +++ /dev/null @@ -1,1160 +0,0 @@ -#include "toy_routine.h" -#include "toy_console_colors.h" - -#include "toy_opcodes.h" -#include "toy_value.h" -#include "toy_string.h" - -#include -#include -#include - -//escapes -void* Toy_private_resizeEscapeArray(Toy_private_EscapeArray* ptr, unsigned int capacity) { - //if you're freeing everything, just return - if (capacity == 0) { - free(ptr); - return NULL; - } - - unsigned int originalCapacity = ptr == NULL ? 0 : ptr->capacity; - unsigned int orignalCount = ptr == NULL ? 0 : ptr->count; - - ptr = (Toy_private_EscapeArray*)realloc(ptr, capacity * sizeof(Toy_private_EscapeEntry_t) + sizeof(Toy_private_EscapeArray)); - - if (ptr == NULL) { - fprintf(stderr, TOY_CC_ERROR "ERROR: Failed to resize an escape array within 'Toy_Routine' from %d to %d capacity\n" TOY_CC_RESET, (int)originalCapacity, (int)capacity); - exit(-1); - } - - ptr->capacity = capacity; - ptr->count = orignalCount; - - return ptr; -} - -//utils -static void expand(unsigned char** handle, unsigned int* capacity, unsigned int* count, unsigned int amount) { - if ((*count) + amount > (*capacity)) { - while ((*count) + amount > (*capacity)) { - (*capacity) = (*capacity) < 8 ? 8 : (*capacity) * 2; - } - (*handle) = realloc((*handle), (*capacity)); - - if ((*handle) == NULL) { - fprintf(stderr, TOY_CC_ERROR "ERROR: Failed to allocate %d space for a part of 'Toy_Routine'\n" TOY_CC_RESET, (int)(*capacity)); - exit(1); - } - } -} - -static void emitByte(unsigned char** handle, unsigned int* capacity, unsigned int* count, unsigned char byte) { - expand(handle, capacity, count, 1); - ((unsigned char*)(*handle))[(*count)++] = byte; -} - -static void emitInt(unsigned char** handle, unsigned int* capacity, unsigned int* count, unsigned int bytes) { - char* ptr = (char*)&bytes; - emitByte(handle, capacity, count, *(ptr++)); - emitByte(handle, capacity, count, *(ptr++)); - emitByte(handle, capacity, count, *(ptr++)); - emitByte(handle, capacity, count, *(ptr++)); -} - -static void emitFloat(unsigned char** handle, unsigned int* capacity, unsigned int* count, float bytes) { - char* ptr = (char*)&bytes; - emitByte(handle, capacity, count, *(ptr++)); - emitByte(handle, capacity, count, *(ptr++)); - emitByte(handle, capacity, count, *(ptr++)); - emitByte(handle, capacity, count, *(ptr++)); -} - -static bool checkForChaining(Toy_Ast* ptr) { - //BUGFIX - if (ptr == NULL) { - return false; - } - - if (ptr->type == TOY_AST_VAR_ASSIGN) { - return true; - } - - if (ptr->type == TOY_AST_UNARY) { - if (ptr->unary.flag >= TOY_AST_FLAG_PREFIX_INCREMENT && ptr->unary.flag <= TOY_AST_FLAG_POSTFIX_DECREMENT) { - return true; - } - } - - return false; -} - -//write instructions based on the AST types -#define EMIT_BYTE(rt, part, byte) \ - emitByte((&((*rt)->part)), &((*rt)->part##Capacity), &((*rt)->part##Count), byte) -#define EMIT_INT(rt, part, bytes) \ - emitInt((&((*rt)->part)), &((*rt)->part##Capacity), &((*rt)->part##Count), bytes) -#define EMIT_FLOAT(rt, part, bytes) \ - emitFloat((&((*rt)->part)), &((*rt)->part##Capacity), &((*rt)->part##Count), bytes) - -//skip bytes, but return the address -#define SKIP_BYTE(rt, part) (EMIT_BYTE(rt, part, 0), ((*rt)->part##Count - 1)) -#define SKIP_INT(rt, part) (EMIT_INT(rt, part, 0), ((*rt)->part##Count - 4)) - -//overwrite a pre-existing position -#define OVERWRITE_INT(rt, part, addr, bytes) \ - emitInt((&((*rt)->part)), &((*rt)->part##Capacity), &(addr), bytes); - -//simply get the address (always an integer) -#define CURRENT_ADDRESS(rt, part) ((*rt)->part##Count) - -static void emitToJumpTable(Toy_Routine** rt, unsigned int startAddr) { - EMIT_INT(rt, code, (*rt)->jumpsCount); //mark the jump index in the code - EMIT_INT(rt, jumps, startAddr); //save address at the jump index -} - -static unsigned int emitString(Toy_Routine** rt, Toy_String* str) { - //4-byte alignment - unsigned int length = str->info.length + 1; - if (length % 4 != 0) { - length += 4 - (length % 4); //ceil - } - - //grab the current start address - unsigned int startAddr = (*rt)->dataCount; - - //move the string into the data section - expand((&((*rt)->data)), &((*rt)->dataCapacity), &((*rt)->dataCount), length); - - if (str->info.type == TOY_STRING_NODE) { - char* buffer = Toy_getStringRawBuffer(str); - memcpy((*rt)->data + (*rt)->dataCount, buffer, str->info.length + 1); - free(buffer); - } - else if (str->info.type == TOY_STRING_LEAF) { - memcpy((*rt)->data + (*rt)->dataCount, str->leaf.data, str->info.length + 1); - } - else if (str->info.type == TOY_STRING_NAME) { - memcpy((*rt)->data + (*rt)->dataCount, str->name.data, str->info.length + 1); - } - - (*rt)->dataCount += length; - - //mark the jump position - emitToJumpTable(rt, startAddr); - - return 1; -} - -static unsigned int writeRoutineCode(Toy_Routine** rt, Toy_Ast* ast); //forward declare for recursion -static unsigned int writeInstructionAssign(Toy_Routine** rt, Toy_AstVarAssign ast, bool chainedAssignment); //forward declare for chaining of var declarations - -static unsigned int writeInstructionValue(Toy_Routine** rt, Toy_AstValue ast) { - EMIT_BYTE(rt, code, TOY_OPCODE_READ); - EMIT_BYTE(rt, code, ast.value.type); - - //emit the raw value based on the type - if (TOY_VALUE_IS_NULL(ast.value)) { - //NOTHING - null's type data is enough - - //4-byte alignment - EMIT_BYTE(rt, code, 0); - EMIT_BYTE(rt, code, 0); - } - else if (TOY_VALUE_IS_BOOLEAN(ast.value)) { - EMIT_BYTE(rt, code, TOY_VALUE_AS_BOOLEAN(ast.value)); - - //4-byte alignment - EMIT_BYTE(rt, code, 0); - } - else if (TOY_VALUE_IS_INTEGER(ast.value)) { - //4-byte alignment - EMIT_BYTE(rt, code, 0); - EMIT_BYTE(rt, code, 0); - - EMIT_INT(rt, code, TOY_VALUE_AS_INTEGER(ast.value)); - } - else if (TOY_VALUE_IS_FLOAT(ast.value)) { - //4-byte alignment - EMIT_BYTE(rt, code, 0); - EMIT_BYTE(rt, code, 0); - - EMIT_FLOAT(rt, code, TOY_VALUE_AS_FLOAT(ast.value)); - } - else if (TOY_VALUE_IS_STRING(ast.value)) { - //4-byte alignment - EMIT_BYTE(rt, code, TOY_STRING_LEAF); //normal string - EMIT_BYTE(rt, code, 0); //can't store the length - - return emitString(rt, TOY_VALUE_AS_STRING(ast.value)); - } - else { - fprintf(stderr, TOY_CC_ERROR "ERROR: Invalid AST type found: Unknown value type\n" TOY_CC_RESET); - exit(-1); - } - - return 1; -} - -static unsigned int writeInstructionUnary(Toy_Routine** rt, Toy_AstUnary ast) { - unsigned int result = 0; - - if (ast.flag == TOY_AST_FLAG_NEGATE) { - result = writeRoutineCode(rt, ast.child); - - EMIT_BYTE(rt, code, TOY_OPCODE_NEGATE); - - //4-byte alignment - EMIT_BYTE(rt, code, 0); - EMIT_BYTE(rt, code, 0); - EMIT_BYTE(rt, code, 0); - } - - else if (ast.flag == TOY_AST_FLAG_PREFIX_INCREMENT || ast.flag == TOY_AST_FLAG_PREFIX_DECREMENT) { //NOTE: tightly coupled to the parser's logic, and somewhat duplicates ACCESS - //read the var name onto the stack - Toy_String* name = TOY_VALUE_AS_STRING(ast.child->value.value); - - EMIT_BYTE(rt, code, TOY_OPCODE_READ); - EMIT_BYTE(rt, code, TOY_VALUE_STRING); - EMIT_BYTE(rt, code, TOY_STRING_NAME); - EMIT_BYTE(rt, code, name->info.length); //store the length (max 255) - - emitString(rt, name); - - //duplicate the var name, then get the value - EMIT_BYTE(rt, code,TOY_OPCODE_DUPLICATE); - EMIT_BYTE(rt, code, TOY_OPCODE_ACCESS); //squeezed - EMIT_BYTE(rt, code, 0); - EMIT_BYTE(rt, code, 0); - - //read the integer '1' - EMIT_BYTE(rt, code, TOY_OPCODE_READ); - EMIT_BYTE(rt, code, TOY_VALUE_INTEGER); - EMIT_BYTE(rt, code, 0); - EMIT_BYTE(rt, code, 0); - - EMIT_INT(rt, code, 1); - - //add (or subtract) the two values, then assign (pops the second duplicate, and leaves value on the stack) - EMIT_BYTE(rt, code, ast.flag == TOY_AST_FLAG_PREFIX_INCREMENT ? TOY_OPCODE_ADD : TOY_OPCODE_SUBTRACT); - EMIT_BYTE(rt, code,TOY_OPCODE_ASSIGN); //squeezed - EMIT_BYTE(rt, code,1); - EMIT_BYTE(rt, code,0); - - //leaves one value on the stack - result = 1; - } - - else if (ast.flag == TOY_AST_FLAG_POSTFIX_INCREMENT || ast.flag == TOY_AST_FLAG_POSTFIX_DECREMENT) { //NOTE: ditto - //read the var name onto the stack - Toy_String* name = TOY_VALUE_AS_STRING(ast.child->value.value); - - EMIT_BYTE(rt, code, TOY_OPCODE_READ); - EMIT_BYTE(rt, code, TOY_VALUE_STRING); - EMIT_BYTE(rt, code, TOY_STRING_NAME); - EMIT_BYTE(rt, code, name->info.length); //store the length (max 255) - - emitString(rt, name); - - //access the value (postfix++ and postfix--) - EMIT_BYTE(rt, code, TOY_OPCODE_ACCESS); - EMIT_BYTE(rt, code,0); - EMIT_BYTE(rt, code,0); - EMIT_BYTE(rt, code,0); - - //read the var name onto the stack (again) - name = TOY_VALUE_AS_STRING(ast.child->value.value); - - EMIT_BYTE(rt, code, TOY_OPCODE_READ); - EMIT_BYTE(rt, code, TOY_VALUE_STRING); - EMIT_BYTE(rt, code, TOY_STRING_NAME); - EMIT_BYTE(rt, code, name->info.length); //store the length (max 255) - - emitString(rt, name); - - //duplicate the var name, then get the value - EMIT_BYTE(rt, code,TOY_OPCODE_DUPLICATE); - EMIT_BYTE(rt, code, TOY_OPCODE_ACCESS); //squeezed - EMIT_BYTE(rt, code,0); - EMIT_BYTE(rt, code,0); - - //read the integer '1' - EMIT_BYTE(rt, code, TOY_OPCODE_READ); - EMIT_BYTE(rt, code, TOY_VALUE_INTEGER); - EMIT_BYTE(rt, code, 0); - EMIT_BYTE(rt, code, 0); - - EMIT_INT(rt, code, 1); - - //add (or subtract) the two values, then assign (pops the second duplicate) - EMIT_BYTE(rt, code, ast.flag == TOY_AST_FLAG_POSTFIX_INCREMENT ? TOY_OPCODE_ADD : TOY_OPCODE_SUBTRACT); - EMIT_BYTE(rt, code,TOY_OPCODE_ASSIGN); //squeezed - EMIT_BYTE(rt, code,0); - EMIT_BYTE(rt, code,0); - - //leaves one value on the stack - result = 1; - } - - else { - fprintf(stderr, TOY_CC_ERROR "ERROR: Invalid AST unary flag found\n" TOY_CC_RESET); - exit(-1); - } - - return result; -} - -static unsigned int writeInstructionBinary(Toy_Routine** rt, Toy_AstBinary ast) { - //left, then right, then the binary's operation - writeRoutineCode(rt, ast.left); - writeRoutineCode(rt, ast.right); - - if (ast.flag == TOY_AST_FLAG_ADD) { - EMIT_BYTE(rt, code,TOY_OPCODE_ADD); - } - else if (ast.flag == TOY_AST_FLAG_SUBTRACT) { - EMIT_BYTE(rt, code,TOY_OPCODE_SUBTRACT); - } - else if (ast.flag == TOY_AST_FLAG_MULTIPLY) { - EMIT_BYTE(rt, code,TOY_OPCODE_MULTIPLY); - } - else if (ast.flag == TOY_AST_FLAG_DIVIDE) { - EMIT_BYTE(rt, code,TOY_OPCODE_DIVIDE); - } - else if (ast.flag == TOY_AST_FLAG_MODULO) { - EMIT_BYTE(rt, code,TOY_OPCODE_MODULO); - } - - else if (ast.flag == TOY_AST_FLAG_CONCAT) { - EMIT_BYTE(rt, code, TOY_OPCODE_CONCAT); - } - else { - fprintf(stderr, TOY_CC_ERROR "ERROR: Invalid AST binary flag found\n" TOY_CC_RESET); - exit(-1); - } - - //4-byte alignment - EMIT_BYTE(rt, code,TOY_OPCODE_PASS); //checked in combined assignments - EMIT_BYTE(rt, code,0); - EMIT_BYTE(rt, code,0); - - return 1; //leaves only 1 value on the stack -} - -static unsigned int writeInstructionBinaryShortCircuit(Toy_Routine** rt, Toy_AstBinaryShortCircuit ast) { - //lhs - writeRoutineCode(rt, ast.left); - - //duplicate the top (so the lhs can be 'returned' by this expression, if needed) - EMIT_BYTE(rt, code,TOY_OPCODE_DUPLICATE); - EMIT_BYTE(rt, code, 0); - EMIT_BYTE(rt, code, 0); - EMIT_BYTE(rt, code, 0); - - // && return the first falsy operand, or the last operand - if (ast.flag == TOY_AST_FLAG_AND) { - EMIT_BYTE(rt, code, TOY_OPCODE_JUMP); - EMIT_BYTE(rt, code, TOY_OP_PARAM_JUMP_RELATIVE); - EMIT_BYTE(rt, code, TOY_OP_PARAM_JUMP_IF_FALSE); - EMIT_BYTE(rt, code, 0); - } - - // || return the first truthy operand, or the last operand - else if (ast.flag == TOY_AST_FLAG_OR) { - EMIT_BYTE(rt, code, TOY_OPCODE_JUMP); - EMIT_BYTE(rt, code, TOY_OP_PARAM_JUMP_RELATIVE); - EMIT_BYTE(rt, code, TOY_OP_PARAM_JUMP_IF_TRUE); - EMIT_BYTE(rt, code, 0); - } - - else { - fprintf(stderr, TOY_CC_ERROR "ERROR: Invalid AST binary short circuit flag found\n" TOY_CC_RESET); - exit(-1); - } - - //parameter address - unsigned int paramAddr = SKIP_INT(rt, code); //parameter to be written later - - //if the lhs value isn't needed, pop it - EMIT_BYTE(rt, code,TOY_OPCODE_ELIMINATE); - EMIT_BYTE(rt, code, 0); - EMIT_BYTE(rt, code, 0); - EMIT_BYTE(rt, code, 0); - - //rhs - writeRoutineCode(rt, ast.right); - - //set the parameter - OVERWRITE_INT(rt, code, paramAddr, CURRENT_ADDRESS(rt, code) - (paramAddr + 4)); - - return 1; //leaves only 1 value on the stack -} - -static unsigned int writeInstructionCompare(Toy_Routine** rt, Toy_AstCompare ast) { - //left, then right, then the compare's operation - writeRoutineCode(rt, ast.left); - writeRoutineCode(rt, ast.right); - - if (ast.flag == TOY_AST_FLAG_COMPARE_EQUAL) { - EMIT_BYTE(rt, code,TOY_OPCODE_COMPARE_EQUAL); - } - else if (ast.flag == TOY_AST_FLAG_COMPARE_NOT) { - EMIT_BYTE(rt, code,TOY_OPCODE_COMPARE_EQUAL); - EMIT_BYTE(rt, code,TOY_OPCODE_NEGATE); //squeezed - EMIT_BYTE(rt, code,0); - EMIT_BYTE(rt, code,0); - - return 1; - } - else if (ast.flag == TOY_AST_FLAG_COMPARE_LESS) { - EMIT_BYTE(rt, code,TOY_OPCODE_COMPARE_LESS); - } - else if (ast.flag == TOY_AST_FLAG_COMPARE_LESS_EQUAL) { - EMIT_BYTE(rt, code,TOY_OPCODE_COMPARE_LESS_EQUAL); - } - else if (ast.flag == TOY_AST_FLAG_COMPARE_GREATER) { - EMIT_BYTE(rt, code,TOY_OPCODE_COMPARE_GREATER); - } - else if (ast.flag == TOY_AST_FLAG_COMPARE_GREATER_EQUAL) { - EMIT_BYTE(rt, code,TOY_OPCODE_COMPARE_GREATER_EQUAL); - } - - else { - fprintf(stderr, TOY_CC_ERROR "ERROR: Invalid AST compare flag found\n" TOY_CC_RESET); - exit(-1); - } - - //4-byte alignment (covers most cases) - EMIT_BYTE(rt, code,0); - EMIT_BYTE(rt, code,0); - EMIT_BYTE(rt, code,0); - - return 1; //leaves only 1 value on the stack -} - -static unsigned int writeInstructionGroup(Toy_Routine** rt, Toy_AstGroup ast) { - //not certain what this leaves - return writeRoutineCode(rt, ast.child); -} - -static unsigned int writeInstructionCompound(Toy_Routine** rt, Toy_AstCompound ast) { - unsigned int result = writeRoutineCode(rt, ast.child); - - if (ast.flag == TOY_AST_FLAG_COMPOUND_ARRAY) { - //signal how many values to read in as array elements - EMIT_BYTE(rt, code, TOY_OPCODE_READ); - EMIT_BYTE(rt, code, TOY_VALUE_ARRAY); - - //4-byte alignment - EMIT_BYTE(rt, code,0); - EMIT_BYTE(rt, code,0); - - //how many elements - EMIT_INT(rt, code, result); - - return 1; //leaves only 1 value on the stack - } - if (ast.flag == TOY_AST_FLAG_COMPOUND_TABLE) { - //signal how many values to read in as table elements - EMIT_BYTE(rt, code, TOY_OPCODE_READ); - EMIT_BYTE(rt, code, TOY_VALUE_TABLE); - - //4-byte alignment - EMIT_BYTE(rt, code,0); - EMIT_BYTE(rt, code,0); - - //how many elements - EMIT_INT(rt, code, result); - - return 1; //leaves only 1 value on the stack - } - else { - fprintf(stderr, TOY_CC_ERROR "ERROR: Invalid AST compound flag found\n" TOY_CC_RESET); - exit(-1); - return 0; - } -} - -static unsigned int writeInstructionAggregate(Toy_Routine** rt, Toy_AstAggregate ast) { - unsigned int result = 0; - - //left, then right - result += writeRoutineCode(rt, ast.left); - result += writeRoutineCode(rt, ast.right); - - if (ast.flag == TOY_AST_FLAG_COLLECTION) { - //collections are handled above - return result; - } - else if (ast.flag == TOY_AST_FLAG_PAIR) { - //pairs are handled above - return result; - } - else if (ast.flag == TOY_AST_FLAG_INDEX) { - //value[index, length] - EMIT_BYTE(rt, code, TOY_OPCODE_INDEX); - EMIT_BYTE(rt, code, result); - - //4-byte alignment - EMIT_BYTE(rt, code,0); - EMIT_BYTE(rt, code,0); - - return 1; //leaves only 1 value on the stack - } - else { - fprintf(stderr, TOY_CC_ERROR "ERROR: Invalid AST aggregate flag found\n" TOY_CC_RESET); - exit(-1); - return 0; - } -} - -static unsigned int writeInstructionAssert(Toy_Routine** rt, Toy_AstAssert ast) { - //the thing to print - writeRoutineCode(rt, ast.child); - writeRoutineCode(rt, ast.message); - - //output the print opcode - EMIT_BYTE(rt, code, TOY_OPCODE_ASSERT); - - //4-byte alignment - EMIT_BYTE(rt, code, ast.message != NULL ? 2 : 1); //arg count - EMIT_BYTE(rt, code,0); - EMIT_BYTE(rt, code,0); - - return 0; -} - -static unsigned int writeInstructionIfThenElse(Toy_Routine** rt, Toy_AstIfThenElse ast) { - //cond-branch - writeRoutineCode(rt, ast.condBranch); - - //emit the jump word (opcode, type, condition, padding) - EMIT_BYTE(rt, code, TOY_OPCODE_JUMP); - EMIT_BYTE(rt, code, TOY_OP_PARAM_JUMP_RELATIVE); - EMIT_BYTE(rt, code, TOY_OP_PARAM_JUMP_IF_FALSE); - EMIT_BYTE(rt, code, 0); - - unsigned int thenParamAddr = SKIP_INT(rt, code); //parameter to be written later - - //emit then-branch - writeRoutineCode(rt, ast.thenBranch); - - if (ast.elseBranch != NULL) { - //emit the jump-to-end (opcode, type, condition, padding) - EMIT_BYTE(rt, code, TOY_OPCODE_JUMP); - EMIT_BYTE(rt, code, TOY_OP_PARAM_JUMP_RELATIVE); - EMIT_BYTE(rt, code, TOY_OP_PARAM_JUMP_ALWAYS); - EMIT_BYTE(rt, code, 0); - - unsigned int elseParamAddr = SKIP_INT(rt, code); //parameter to be written later - - //specify the starting position for the else branch - OVERWRITE_INT(rt, code, thenParamAddr, CURRENT_ADDRESS(rt, code) - (thenParamAddr + 4)); - - //emit the else branch - writeRoutineCode(rt, ast.elseBranch); - - //specify the ending position for the else branch - OVERWRITE_INT(rt, code, elseParamAddr, CURRENT_ADDRESS(rt, code) - (elseParamAddr + 4)); - } - - else { - //without an else branch, set the jump destination and move on - OVERWRITE_INT(rt, code, thenParamAddr, CURRENT_ADDRESS(rt, code) - (thenParamAddr + 4)); - } - - return 0; -} - -static unsigned int writeInstructionWhileThen(Toy_Routine** rt, Toy_AstWhileThen ast) { - //begin - unsigned int beginAddr = CURRENT_ADDRESS(rt, code); - - //cond-branch - writeRoutineCode(rt, ast.condBranch); - - //emit the jump word (opcode, type, condition, padding) - EMIT_BYTE(rt, code, TOY_OPCODE_JUMP); - EMIT_BYTE(rt, code, TOY_OP_PARAM_JUMP_RELATIVE); - EMIT_BYTE(rt, code, TOY_OP_PARAM_JUMP_IF_FALSE); - EMIT_BYTE(rt, code, 0); - - unsigned int paramAddr = SKIP_INT(rt, code); //parameter to be written later - - //emit then-branch - writeRoutineCode(rt, ast.thenBranch); - - //jump to begin to repeat the conditional test - EMIT_BYTE(rt, code, TOY_OPCODE_JUMP); - EMIT_BYTE(rt, code, TOY_OP_PARAM_JUMP_RELATIVE); - EMIT_BYTE(rt, code, TOY_OP_PARAM_JUMP_ALWAYS); - EMIT_BYTE(rt, code, 0); - - EMIT_INT(rt, code, beginAddr - (CURRENT_ADDRESS(rt, code) + 4)); //this sets a negative value - - //set the exit parameter for the cond - OVERWRITE_INT(rt, code, paramAddr, CURRENT_ADDRESS(rt, code) - (paramAddr + 4)); - - //set the break & continue data - while ((*rt)->breakEscapes->count > 0) { - //extract - unsigned int addr = (*rt)->breakEscapes->data[(*rt)->breakEscapes->count - 1].addr; - unsigned int depth = (*rt)->breakEscapes->data[(*rt)->breakEscapes->count - 1].depth; - - unsigned int diff = depth - (*rt)->currentScopeDepth; - - OVERWRITE_INT(rt, code, addr, CURRENT_ADDRESS(rt, code) - (addr + 8)); //tell break to come here AFTER reading the instruction - OVERWRITE_INT(rt, code, addr, diff); - - //tick down - (*rt)->breakEscapes->count--; - } - - while ((*rt)->continueEscapes->count > 0) { - //extract - unsigned int addr = (*rt)->continueEscapes->data[(*rt)->continueEscapes->count - 1].addr; - unsigned int depth = (*rt)->continueEscapes->data[(*rt)->continueEscapes->count - 1].depth; - - unsigned int diff = depth - (*rt)->currentScopeDepth; - - OVERWRITE_INT(rt, code, addr, CURRENT_ADDRESS(rt, code) - (addr + 8)); //tell continue to return to the start AFTER reading the instruction - OVERWRITE_INT(rt, code, addr, diff); - - //tick down - (*rt)->continueEscapes->count--; - } - - return 0; -} - -static unsigned int writeInstructionBreak(Toy_Routine** rt, Toy_AstBreak ast) { - //unused - (void)ast; - - //escapes are always relative - EMIT_BYTE(rt, code, TOY_OPCODE_ESCAPE); - EMIT_BYTE(rt, code, 0); - EMIT_BYTE(rt, code, 0); - EMIT_BYTE(rt, code, 0); - - unsigned int addr = SKIP_INT(rt, code); - (void)SKIP_INT(rt, code); //empty space for depth - - //expand the escape array if needed - if ((*rt)->breakEscapes->capacity <= (*rt)->breakEscapes->count) { - (*rt)->breakEscapes = Toy_private_resizeEscapeArray((*rt)->breakEscapes, (*rt)->breakEscapes->capacity * TOY_ESCAPE_EXPANSION_RATE); - } - - //store for later - (*rt)->breakEscapes->data[(*rt)->breakEscapes->count++] = (Toy_private_EscapeEntry_t){ .addr = addr, .depth = (*rt)->currentScopeDepth }; - - return 0; -} - -static unsigned int writeInstructionContinue(Toy_Routine** rt, Toy_AstContinue ast) { - //unused - (void)ast; - - //escapes are always relative - EMIT_BYTE(rt, code, TOY_OPCODE_ESCAPE); - EMIT_BYTE(rt, code, 0); - EMIT_BYTE(rt, code, 0); - EMIT_BYTE(rt, code, 0); - - unsigned int addr = SKIP_INT(rt, code); - (void)SKIP_INT(rt, code); //empty space for depth - - //expand the escape array if needed - if ((*rt)->continueEscapes->capacity <= (*rt)->continueEscapes->count) { - (*rt)->continueEscapes = Toy_private_resizeEscapeArray((*rt)->continueEscapes, (*rt)->continueEscapes->capacity * TOY_ESCAPE_EXPANSION_RATE); - } - - //store for later - (*rt)->continueEscapes->data[(*rt)->continueEscapes->count++] = (Toy_private_EscapeEntry_t){ .addr = addr, .depth = (*rt)->currentScopeDepth }; - - return 0; -} - -static unsigned int writeInstructionPrint(Toy_Routine** rt, Toy_AstPrint ast) { - //the thing to print - writeRoutineCode(rt, ast.child); - - //output the print opcode - EMIT_BYTE(rt, code,TOY_OPCODE_PRINT); - - //4-byte alignment - EMIT_BYTE(rt, code,0); - EMIT_BYTE(rt, code,0); - EMIT_BYTE(rt, code,0); - - return 0; -} - -static unsigned int writeInstructionVarDeclare(Toy_Routine** rt, Toy_AstVarDeclare ast) { - //if we're dealing with chained assignments, hijack the next assignment with 'chainedAssignment' set to true - if (checkForChaining(ast.expr)) { - writeInstructionAssign(rt, ast.expr->varAssign, true); - } - else { - writeRoutineCode(rt, ast.expr); //default value - } - - //delcare with the given name string - EMIT_BYTE(rt, code, TOY_OPCODE_DECLARE); - EMIT_BYTE(rt, code, Toy_getNameStringVarType(ast.name)); - EMIT_BYTE(rt, code, ast.name->info.length); //quick optimisation to skip a 'strlen()' call - EMIT_BYTE(rt, code, Toy_getNameStringVarConstant(ast.name) ? 1 : 0); //check for constness - - emitString(rt, ast.name); - - return 0; -} - -static unsigned int writeInstructionAssign(Toy_Routine** rt, Toy_AstVarAssign ast, bool chainedAssignment) { - unsigned int result = 0; - - //target is a name string - if (ast.target->type == TOY_AST_VALUE && TOY_VALUE_IS_STRING(ast.target->value.value) && TOY_VALUE_AS_STRING(ast.target->value.value)->info.type == TOY_STRING_NAME) { - //name string - Toy_String* target = TOY_VALUE_AS_STRING(ast.target->value.value); - - //emit the name string - EMIT_BYTE(rt, code, TOY_OPCODE_READ); - EMIT_BYTE(rt, code, TOY_VALUE_STRING); - EMIT_BYTE(rt, code, TOY_STRING_NAME); - EMIT_BYTE(rt, code, target->info.length); //store the length (max 255) - - emitString(rt, target); - } - - //target is an indexing of some compound value - else if (ast.target->type == TOY_AST_AGGREGATE && ast.target->aggregate.flag == TOY_AST_FLAG_INDEX) { - writeRoutineCode(rt, ast.target->aggregate.left); //any deeper indexing will just work, using reference values - writeRoutineCode(rt, ast.target->aggregate.right); //key - - //if we're dealing with chained assignments, hijack the next assignment with 'chainedAssignment' set to true - if (checkForChaining(ast.expr)) { - result += writeInstructionAssign(rt, ast.expr->varAssign, true); - } - else { - result += writeRoutineCode(rt, ast.expr); //default value - } - - EMIT_BYTE(rt, code, TOY_OPCODE_ASSIGN_COMPOUND); //uses the top three values on the stack - EMIT_BYTE(rt, code, chainedAssignment); - EMIT_BYTE(rt, code,0); - EMIT_BYTE(rt, code,0); - - return result + (chainedAssignment ? 1 : 0); - } - - else { - //unknown target - fprintf(stderr, TOY_CC_ERROR "COMPILER ERROR: Invalid AST type found: Malformed assignment target\n" TOY_CC_RESET); - (*rt)->panic = true; - return 0; - } - - //determine RHS, include duplication if needed - if (ast.flag == TOY_AST_FLAG_ASSIGN) { - //if we're dealing with chained assignments, hijack the next assignment with 'chainedAssignment' set to true - if (checkForChaining(ast.expr)) { - result += writeInstructionAssign(rt, ast.expr->varAssign, true); - } - else { - result += writeRoutineCode(rt, ast.expr); //default value - } - - EMIT_BYTE(rt, code, TOY_OPCODE_ASSIGN); - EMIT_BYTE(rt, code, chainedAssignment); - EMIT_BYTE(rt, code,0); - EMIT_BYTE(rt, code,0); - } - else if (ast.flag == TOY_AST_FLAG_ADD_ASSIGN) { - EMIT_BYTE(rt, code,TOY_OPCODE_DUPLICATE); - EMIT_BYTE(rt, code,TOY_OPCODE_ACCESS); //squeezed - EMIT_BYTE(rt, code,0); - EMIT_BYTE(rt, code,0); - - //if we're dealing with chained assignments, hijack the next assignment with 'chainedAssignment' set to true - if (checkForChaining(ast.expr)) { - result += writeInstructionAssign(rt, ast.expr->varAssign, true); - } - else { - result += writeRoutineCode(rt, ast.expr); //default value - } - - EMIT_BYTE(rt, code,TOY_OPCODE_ADD); - EMIT_BYTE(rt, code,TOY_OPCODE_ASSIGN); //squeezed - EMIT_BYTE(rt, code, chainedAssignment); - EMIT_BYTE(rt, code,0); - } - else if (ast.flag == TOY_AST_FLAG_SUBTRACT_ASSIGN) { - EMIT_BYTE(rt, code,TOY_OPCODE_DUPLICATE); - EMIT_BYTE(rt, code,TOY_OPCODE_ACCESS); //squeezed - EMIT_BYTE(rt, code,0); - EMIT_BYTE(rt, code,0); - - //if we're dealing with chained assignments, hijack the next assignment with 'chainedAssignment' set to true - if (checkForChaining(ast.expr)) { - result += writeInstructionAssign(rt, ast.expr->varAssign, true); - } - else { - result += writeRoutineCode(rt, ast.expr); //default value - } - - EMIT_BYTE(rt, code,TOY_OPCODE_SUBTRACT); - EMIT_BYTE(rt, code,TOY_OPCODE_ASSIGN); //squeezed - EMIT_BYTE(rt, code, chainedAssignment); - EMIT_BYTE(rt, code,0); - } - else if (ast.flag == TOY_AST_FLAG_MULTIPLY_ASSIGN) { - EMIT_BYTE(rt, code,TOY_OPCODE_DUPLICATE); - EMIT_BYTE(rt, code,TOY_OPCODE_ACCESS); //squeezed - EMIT_BYTE(rt, code,0); - EMIT_BYTE(rt, code,0); - - //if we're dealing with chained assignments, hijack the next assignment with 'chainedAssignment' set to true - if (checkForChaining(ast.expr)) { - result += writeInstructionAssign(rt, ast.expr->varAssign, true); - } - else { - result += writeRoutineCode(rt, ast.expr); //default value - } - - EMIT_BYTE(rt, code,TOY_OPCODE_MULTIPLY); - EMIT_BYTE(rt, code,TOY_OPCODE_ASSIGN); //squeezed - EMIT_BYTE(rt, code, chainedAssignment); - EMIT_BYTE(rt, code,0); - } - else if (ast.flag == TOY_AST_FLAG_DIVIDE_ASSIGN) { - EMIT_BYTE(rt, code,TOY_OPCODE_DUPLICATE); - EMIT_BYTE(rt, code,TOY_OPCODE_ACCESS); //squeezed - EMIT_BYTE(rt, code,0); - EMIT_BYTE(rt, code,0); - - //if we're dealing with chained assignments, hijack the next assignment with 'chainedAssignment' set to true - if (checkForChaining(ast.expr)) { - result += writeInstructionAssign(rt, ast.expr->varAssign, true); - } - else { - result += writeRoutineCode(rt, ast.expr); //default value - } - - EMIT_BYTE(rt, code,TOY_OPCODE_DIVIDE); - EMIT_BYTE(rt, code,TOY_OPCODE_ASSIGN); //squeezed - EMIT_BYTE(rt, code, chainedAssignment); - EMIT_BYTE(rt, code,0); - } - else if (ast.flag == TOY_AST_FLAG_MODULO_ASSIGN) { - EMIT_BYTE(rt, code,TOY_OPCODE_DUPLICATE); - EMIT_BYTE(rt, code,TOY_OPCODE_ACCESS); //squeezed - EMIT_BYTE(rt, code,0); - EMIT_BYTE(rt, code,0); - - //if we're dealing with chained assignments, hijack the next assignment with 'chainedAssignment' set to true - if (checkForChaining(ast.expr)) { - result += writeInstructionAssign(rt, ast.expr->varAssign, true); - } - else { - result += writeRoutineCode(rt, ast.expr); //default value - } - - EMIT_BYTE(rt, code,TOY_OPCODE_MODULO); - EMIT_BYTE(rt, code,TOY_OPCODE_ASSIGN); //squeezed - EMIT_BYTE(rt, code, chainedAssignment); - EMIT_BYTE(rt, code,0); - } - - else { - fprintf(stderr, TOY_CC_ERROR "ERROR: Invalid AST assign flag found\n" TOY_CC_RESET); - exit(-1); - } - - return result + (chainedAssignment ? 1 : 0); -} - -static unsigned int writeInstructionAccess(Toy_Routine** rt, Toy_AstVarAccess ast) { - if (!(ast.child->type == TOY_AST_VALUE && TOY_VALUE_IS_STRING(ast.child->value.value) && TOY_VALUE_AS_STRING(ast.child->value.value)->info.type == TOY_STRING_NAME)) { - fprintf(stderr, TOY_CC_ERROR "COMPILER ERROR: Found a non-name-string in a value node when trying to write access\n" TOY_CC_RESET); - exit(-1); - } - - Toy_String* name = TOY_VALUE_AS_STRING(ast.child->value.value); - - //push the name - EMIT_BYTE(rt, code, TOY_OPCODE_READ); - EMIT_BYTE(rt, code, TOY_VALUE_STRING); - EMIT_BYTE(rt, code, TOY_STRING_NAME); - EMIT_BYTE(rt, code, name->info.length); //store the length (max 255) - - emitString(rt, name); - - //convert name to value - EMIT_BYTE(rt, code, TOY_OPCODE_ACCESS); - EMIT_BYTE(rt, code,0); - EMIT_BYTE(rt, code,0); - EMIT_BYTE(rt, code,0); - - return 1; -} - -//routine structure -// static void writeRoutineParam(Toy_Routine* rt) { -// // -// } - -static unsigned int writeRoutineCode(Toy_Routine** rt, Toy_Ast* ast) { - if (ast == NULL) { - return 0; - } - - //if an error occured, just exit - if (rt == NULL || (*rt) == NULL || (*rt)->panic) { - return 0; - } - - //NOTE: 'result' is used to in 'writeInstructionAggregate()' - unsigned int result = 0; - - //determine how to write each instruction based on the Ast - switch(ast->type) { - case TOY_AST_BLOCK: - if (ast->block.innerScope) { - EMIT_BYTE(rt, code, TOY_OPCODE_SCOPE_PUSH); - EMIT_BYTE(rt, code, 0); - EMIT_BYTE(rt, code, 0); - EMIT_BYTE(rt, code, 0); - - (*rt)->currentScopeDepth++; - } - - result += writeRoutineCode(rt, ast->block.child); - result += writeRoutineCode(rt, ast->block.next); - - if (ast->block.innerScope) { - EMIT_BYTE(rt, code, TOY_OPCODE_SCOPE_POP); - EMIT_BYTE(rt, code, 0); - EMIT_BYTE(rt, code, 0); - EMIT_BYTE(rt, code, 0); - - (*rt)->currentScopeDepth--; - } - break; - - case TOY_AST_VALUE: - result += writeInstructionValue(rt, ast->value); - break; - - case TOY_AST_UNARY: - result += writeInstructionUnary(rt, ast->unary); - break; - - case TOY_AST_BINARY: - result += writeInstructionBinary(rt, ast->binary); - break; - - case TOY_AST_BINARY_SHORT_CIRCUIT: - result += writeInstructionBinaryShortCircuit(rt, ast->binaryShortCircuit); - break; - - case TOY_AST_COMPARE: - result += writeInstructionCompare(rt, ast->compare); - break; - - case TOY_AST_GROUP: - result += writeInstructionGroup(rt, ast->group); - break; - - case TOY_AST_COMPOUND: - result += writeInstructionCompound(rt, ast->compound); - break; - - case TOY_AST_AGGREGATE: - result += writeInstructionAggregate(rt, ast->aggregate); - break; - - case TOY_AST_ASSERT: - result += writeInstructionAssert(rt, ast->assert); - break; - - case TOY_AST_IF_THEN_ELSE: - result += writeInstructionIfThenElse(rt, ast->ifThenElse); - break; - - case TOY_AST_WHILE_THEN: - result += writeInstructionWhileThen(rt, ast->whileThen); - break; - - case TOY_AST_BREAK: - result += writeInstructionBreak(rt, ast->breakPoint); - break; - - case TOY_AST_CONTINUE: - result += writeInstructionContinue(rt, ast->continuePoint); - break; - - case TOY_AST_PRINT: - result += writeInstructionPrint(rt, ast->print); - break; - - case TOY_AST_VAR_DECLARE: - result += writeInstructionVarDeclare(rt, ast->varDeclare); - break; - - case TOY_AST_VAR_ASSIGN: - result += writeInstructionAssign(rt, ast->varAssign, false); - break; - - case TOY_AST_VAR_ACCESS: - result += writeInstructionAccess(rt, ast->varAccess); - break; - - case TOY_AST_PASS: - //NO-OP - break; - - //meta instructions are disallowed - case TOY_AST_ERROR: - fprintf(stderr, TOY_CC_ERROR "COMPILER ERROR: Invalid AST type found: Unknown 'error'\n" TOY_CC_RESET); - (*rt)->panic = true; - break; - - case TOY_AST_END: - fprintf(stderr, TOY_CC_ERROR "COMPILER ERROR: Invalid AST type found: Unknown 'end'\n" TOY_CC_RESET); - (*rt)->panic = true; - break; - } - - return result; -} - -static void* writeRoutine(Toy_Routine* rt, Toy_Ast* ast) { - //code - writeRoutineCode(&rt, ast); - EMIT_BYTE(&rt, code, TOY_OPCODE_RETURN); //temp terminator - EMIT_BYTE(&rt, code, 0); //4-byte alignment - EMIT_BYTE(&rt, code, 0); - EMIT_BYTE(&rt, code, 0); - - //if an error occurred, just exit - if (rt->panic) { - return NULL; - } - - //write the header and combine the parts - unsigned char* buffer = NULL; - unsigned int capacity = 0, count = 0; - // int paramAddr = 0, subsAddr = 0; - int codeAddr = 0; - int jumpsAddr = 0; - int dataAddr = 0; - - emitInt(&buffer, &capacity, &count, 0); //total size (overwritten later) - emitInt(&buffer, &capacity, &count, rt->paramCount); //param size - emitInt(&buffer, &capacity, &count, rt->jumpsCount); //jumps size - emitInt(&buffer, &capacity, &count, rt->dataCount); //data size - emitInt(&buffer, &capacity, &count, rt->subsCount); //routine size - - //generate blank spaces, cache their positions in the *Addr variables (for storing the start positions) - if (rt->paramCount > 0) { - // paramAddr = count; - emitInt(&buffer, &capacity, &count, 0); //params - } - if (rt->codeCount > 0) { - codeAddr = count; - emitInt(&buffer, &capacity, &count, 0); //code - } - if (rt->jumpsCount > 0) { - jumpsAddr = count; - emitInt(&buffer, &capacity, &count, 0); //jumps - } - if (rt->dataCount > 0) { - dataAddr = count; - emitInt(&buffer, &capacity, &count, 0); //data - } - if (rt->subsCount > 0) { - // subsAddr = count; - emitInt(&buffer, &capacity, &count, 0); //subs - } - - //append various parts to the buffer - //TODO: param region - - if (rt->codeCount > 0) { - expand(&buffer, &capacity, &count, rt->codeCount); - memcpy((buffer + count), rt->code, rt->codeCount); - - *((int*)(buffer + codeAddr)) = count; - count += rt->codeCount; - } - - if (rt->jumpsCount > 0) { - expand(&buffer, &capacity, &count, rt->jumpsCount); - memcpy((buffer + count), rt->jumps, rt->jumpsCount); - - *((int*)(buffer + jumpsAddr)) = count; - count += rt->jumpsCount; - } - - if (rt->dataCount > 0) { - expand(&buffer, &capacity, &count, rt->dataCount); - memcpy((buffer + count), rt->data, rt->dataCount); - - *((int*)(buffer + dataAddr)) = count; - count += rt->dataCount; - } - - //TODO: subroutine region - - //finally, record the total size within the header, and return the result - *((int*)buffer) = count; - - return buffer; -} - -//exposed functions -void* Toy_compileRoutine(Toy_Ast* ast) { - //setup - Toy_Routine rt; - - rt.param = NULL; - rt.paramCapacity = 0; - rt.paramCount = 0; - - rt.code = NULL; - rt.codeCapacity = 0; - rt.codeCount = 0; - - rt.jumps = NULL; - rt.jumpsCapacity = 0; - rt.jumpsCount = 0; - - rt.data = NULL; - rt.dataCapacity = 0; - rt.dataCount = 0; - - rt.subs = NULL; - rt.subsCapacity = 0; - rt.subsCount = 0; - - rt.currentScopeDepth = 0; - rt.breakEscapes = Toy_private_resizeEscapeArray(NULL, TOY_ESCAPE_INITIAL_CAPACITY); - rt.continueEscapes = Toy_private_resizeEscapeArray(NULL, TOY_ESCAPE_INITIAL_CAPACITY); - - rt.panic = false; - - //build - void * buffer = writeRoutine(&rt, ast); - - //cleanup - Toy_private_resizeEscapeArray(rt.breakEscapes, 0); - Toy_private_resizeEscapeArray(rt.continueEscapes, 0); - - free(rt.param); - free(rt.code); - free(rt.jumps); - free(rt.data); - free(rt.subs); - - return buffer; -} diff --git a/source/toy_vm.c b/source/toy_vm.c index 0fc21c4..aede964 100644 --- a/source/toy_vm.c +++ b/source/toy_vm.c @@ -10,16 +10,16 @@ //utilities #define READ_BYTE(vm) \ - vm->module[vm->programCounter++] + vm->code[vm->programCounter++] #define READ_UNSIGNED_INT(vm) \ - *((unsigned int*)(vm->module + readPostfixUtil(&(vm->programCounter), 4))) + *((unsigned int*)(vm->code + readPostfixUtil(&(vm->programCounter), 4))) #define READ_INT(vm) \ - *((int*)(vm->module + readPostfixUtil(&(vm->programCounter), 4))) + *((int*)(vm->code + readPostfixUtil(&(vm->programCounter), 4))) #define READ_FLOAT(vm) \ - *((float*)(vm->module + readPostfixUtil(&(vm->programCounter), 4))) + *((float*)(vm->code + readPostfixUtil(&(vm->programCounter), 4))) static inline int readPostfixUtil(unsigned int* ptr, int amount) { int ret = *ptr; @@ -66,10 +66,10 @@ static void processRead(Toy_VM* vm) { int len = (int)READ_BYTE(vm); //only needed for name strings //grab the jump as an integer - unsigned int jump = *((int*)(vm->module + vm->jumpsAddr + READ_INT(vm))); + unsigned int jump = *((int*)(vm->code + vm->jumpsAddr + READ_INT(vm))); //jumps are relative to the data address - char* cstring = (char*)(vm->module + vm->dataAddr + jump); + char* cstring = (char*)(vm->code + vm->dataAddr + jump); //build a string from the data section if (stringType == TOY_STRING_LEAF) { @@ -192,10 +192,10 @@ static void processDeclare(Toy_VM* vm) { bool constant = READ_BYTE(vm); //constness //grab the jump - unsigned int jump = *(unsigned int*)(vm->module + vm->jumpsAddr + READ_INT(vm)); + unsigned int jump = *(unsigned int*)(vm->code + vm->jumpsAddr + READ_INT(vm)); //grab the data - char* cstring = (char*)(vm->module + vm->dataAddr + jump); + char* cstring = (char*)(vm->code + vm->dataAddr + jump); //build the name string Toy_String* name = Toy_createNameStringLength(&vm->stringBucket, cstring, len, type, constant); @@ -939,121 +939,17 @@ static void process(Toy_VM* vm) { } //exposed functions -void Toy_initVM(Toy_VM* vm) { - //clear the stack, scope and memory - vm->stringBucket = NULL; - vm->scopeBucket = NULL; - vm->stack = NULL; - vm->scope = NULL; - - Toy_resetVM(vm); -} - -void Toy_bindVM(Toy_VM* vm, struct Toy_Bytecode* bc) { - if (bc->ptr[0] != TOY_VERSION_MAJOR || bc->ptr[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, bc->ptr[0], bc->ptr[1], bc->ptr[2]); - exit(-1); - } - - if (bc->ptr[2] != TOY_VERSION_PATCH) { - fprintf(stderr, TOY_CC_WARN "WARNING: Wrong bytecode version found: expected %d.%d.%d found %d.%d.%d, continuing\n" TOY_CC_RESET, TOY_VERSION_MAJOR, TOY_VERSION_MINOR, TOY_VERSION_PATCH, bc->ptr[0], bc->ptr[1], bc->ptr[2]); - } - - if (strcmp((char*)(bc->ptr + 3), TOY_VERSION_BUILD) != 0) { - fprintf(stderr, TOY_CC_WARN "WARNING: Wrong bytecode build info found: expected '%s' found '%s', continuing\n" TOY_CC_RESET, TOY_VERSION_BUILD, (char*)(bc->ptr + 3)); - } - - //offset by the header size - int offset = 3 + strlen(TOY_VERSION_BUILD) + 1; - if (offset % 4 != 0) { - offset += 4 - (offset % 4); //ceil - } - - if (bc->moduleCount != 0) { //tmp check, just in case the bytecode is empty; will rework this when module packing works - //delegate to a more specialized function - Toy_bindVMToModule(vm, bc->ptr + offset); - } -} - -void Toy_bindVMToModule(Toy_VM* vm, unsigned char* module) { - vm->module = module; - - //read the header metadata - vm->moduleSize = READ_UNSIGNED_INT(vm); - vm->paramSize = READ_UNSIGNED_INT(vm); - vm->jumpsSize = READ_UNSIGNED_INT(vm); - vm->dataSize = READ_UNSIGNED_INT(vm); - vm->subsSize = READ_UNSIGNED_INT(vm); - - //read the header addresses - if (vm->paramSize > 0) { - vm->paramAddr = READ_UNSIGNED_INT(vm); - } - - vm->codeAddr = READ_UNSIGNED_INT(vm); //required - - if (vm->jumpsSize > 0) { - vm->jumpsAddr = READ_UNSIGNED_INT(vm); - } - - if (vm->dataSize > 0) { - vm->dataAddr = READ_UNSIGNED_INT(vm); - } - - if (vm->subsSize > 0) { - vm->subsAddr = READ_UNSIGNED_INT(vm); - } - - //allocate the stack, scope, and memory (skip if already in use) - if (vm->stringBucket == NULL) { - vm->stringBucket = Toy_allocateBucket(TOY_BUCKET_IDEAL); - } - if (vm->scopeBucket == NULL) { - vm->scopeBucket = Toy_allocateBucket(TOY_BUCKET_IDEAL); - } - if (vm->stack == NULL) { - vm->stack = Toy_allocateStack(); - } - if (vm->scope == NULL) { - vm->scope = Toy_pushScope(&vm->scopeBucket, NULL); - } -} - -void Toy_runVM(Toy_VM* vm) { - //NO-OP on empty VMs - if (vm->module == NULL) { - return; - } - - //TODO: read params into scope - - //prep the program counter for execution - vm->programCounter = vm->codeAddr; - - //begin - process(vm); -} - -void Toy_freeVM(Toy_VM* vm) { - //clear the stack, scope and memory - Toy_freeStack(vm->stack); - Toy_popScope(vm->scope); - Toy_freeBucket(&vm->stringBucket); - Toy_freeBucket(&vm->scopeBucket); -} - void Toy_resetVM(Toy_VM* vm) { - vm->module = NULL; - vm->moduleSize = 0; + vm->code = NULL; - vm->paramSize = 0; - vm->jumpsSize = 0; - vm->dataSize = 0; - vm->subsSize = 0; + vm->jumpsCount = 0; + vm->paramCount = 0; + vm->dataCount = 0; + vm->subsCount = 0; - vm->paramAddr = 0; vm->codeAddr = 0; vm->jumpsAddr = 0; + vm->paramAddr = 0; vm->dataAddr = 0; vm->subsAddr = 0; @@ -1061,5 +957,67 @@ void Toy_resetVM(Toy_VM* vm) { Toy_resetStack(&vm->stack); - //NOTE: scope and memory are not altered during resets + //NOTE: scope and buckets are not altered during resets +} + +void Toy_initVM(Toy_VM* vm) { + //create persistent memory + vm->scope = NULL; + vm->stack = Toy_allocateStack(); + vm->stringBucket = Toy_allocateBucket(TOY_BUCKET_IDEAL); + vm->scopeBucket = Toy_allocateBucket(TOY_BUCKET_IDEAL); + + Toy_resetVM(vm); +} + +void Toy_inheritVM(Toy_VM* vm, Toy_VM* parent) { + //inherent persistent memory + vm->scope = NULL; + vm->stack = Toy_allocateStack(); + vm->stringBucket = parent->stringBucket; + vm->scopeBucket = parent->scopeBucket; + + //TODO: parent bucket pointers are updated after function calls + + Toy_resetVM(vm); +} + +void Toy_bindVMToModule(Toy_VM* vm, Toy_Module* module) { + vm->code = module->code; + + vm->jumpsCount = module->jumpsCount; + vm->paramCount = module->paramCount; + vm->dataCount = module->dataCount; + vm->subsCount = module->subsCount; + + vm->codeAddr = module->codeAddr; + vm->jumpsAddr = module->jumpsAddr; + vm->paramAddr = module->paramAddr; + vm->dataAddr = module->dataAddr; + vm->subsAddr = module->subsAddr; + + vm->scope = Toy_pushScope(&vm->scopeBucket, module->scopePtr); //new scope for the upcoming run +} + +void Toy_runVM(Toy_VM* vm) { + //TODO: read params into scope + + //prep the program counter for execution + vm->programCounter = vm->codeAddr; + + //begin + process(vm); + + //TODO: add return value extraction +} + +void Toy_freeVM(Toy_VM* vm) { + Toy_resetVM(vm); + + Toy_popScope(vm->scope); + + //clear the persistent memory + Toy_freeStack(vm->stack); + Toy_freeBucket(&vm->stringBucket); + Toy_freeBucket(&vm->scopeBucket); } diff --git a/source/toy_vm.h b/source/toy_vm.h index 001cf2a..0f8d07d 100644 --- a/source/toy_vm.h +++ b/source/toy_vm.h @@ -2,9 +2,9 @@ #include "toy_common.h" -#include "toy_bytecode.h" #include "toy_bucket.h" #include "toy_scope.h" +#include "toy_module.h" #include "toy_value.h" #include "toy_string.h" @@ -14,40 +14,42 @@ typedef struct Toy_VM { //raw instructions to be executed - unsigned char* module; //URGENT: rename to 'code' - unsigned int moduleSize; + unsigned char* code; - unsigned int paramSize; - unsigned int jumpsSize; - unsigned int dataSize; - unsigned int subsSize; + //metadata + unsigned int jumpsCount; + unsigned int paramCount; + unsigned int dataCount; + unsigned int subsCount; - unsigned int paramAddr; unsigned int codeAddr; unsigned int jumpsAddr; + unsigned int paramAddr; unsigned int dataAddr; unsigned int subsAddr; + //execution utils unsigned int programCounter; - //stack - immediate-level values only - Toy_Stack* stack; - //scope - block-level key/value pairs Toy_Scope* scope; + //stack - immediate-level values only + Toy_Stack* stack; + //easy access to memory Toy_Bucket* stringBucket; //stores the string literals - Toy_Bucket* scopeBucket; //stores the scopes + Toy_Bucket* scopeBucket; //stores the scope instances TODO: is this separation needed? } Toy_VM; -TOY_API void Toy_initVM(Toy_VM* vm); -TOY_API void Toy_bindVM(Toy_VM* vm, struct Toy_Bytecode* bc); //process the version data -TOY_API void Toy_bindVMToModule(Toy_VM* vm, unsigned char* module); //process the module only +TOY_API void Toy_resetVM(Toy_VM* vm); //persists memory +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_bindVMToModule(Toy_VM* vm, Toy_Module* module); TOY_API void Toy_runVM(Toy_VM* vm); + TOY_API void Toy_freeVM(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 (hook system for external libraries)