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)