diff --git a/.notes/disassembly.txt b/.notes/disassembly.txt new file mode 100644 index 0000000..57b2c2d --- /dev/null +++ b/.notes/disassembly.txt @@ -0,0 +1,33 @@ + +//source + +while (true) { + print "1"; + break; + print "2"; +} + +print "3"; + +//asm + +0 TOY_OPCODE_READ [TOY_VALUE_BOOLEAN, true, -] +4 TOY_OPCODE_JUMP [TOY_OP_PARAM_JUMP_RELATIVE, TOY_OP_PARAM_JUMP_IF_FALSE, -] +8 52 (jumps to end '64' after this JUMP instruction) +12 TOY_OPCODE_SCOPE_PUSH +16 TOY_OPCODE_READ [TOY_VALUE_STRING, TOY_STRING_LEAF, 0] +20 0 (string "1") +24 TOY_OPCODE_PRINT +28 TOY_OPCODE_ESCAPE +32 0? (addr) +36 0? (diff) +40 TOY_OPCODE_READ [TOY_VALUE_STRING, TOY_STRING_LEAF, 0] +44 4 (string "2") +48 TOY_OPCODE_PRINT +52 TOY_OPCODE_SCOPE_POP +56 TOY_OPCODE_JUMP [TOY_OP_PARAM_JUMP_RELATIVE, TOY_OP_PARAM_JUMP_ALWAYS, -] +60 -64 (jumps to start '0' after this JUMP instruction) +64 TOY_OPCODE_READ [TOY_VALUE_STRING, TOY_STRING_LEAF, 0] +68 8 (string "3") +72 TOY_OPCODE_PRINT +76 TOY_OPCODE_RETURN diff --git a/scripts/breakdancing.toy b/scripts/breakdancing.toy new file mode 100644 index 0000000..d1022a6 --- /dev/null +++ b/scripts/breakdancing.toy @@ -0,0 +1,48 @@ + +while (true) { + print "1"; + break; + print "2"; +} + +print "3"; + + + +while (true) { + print 1; + while (true) { + print 2; + if (true) { + print 3; + while (true) { + print 4; + break; + print 5; + } + print 6; + } + print 7; + } + print 8; +} + +print 9; + + + +while (true) { + print 1; + while (true) { + print 2; + if (true) { + print 3; + break; + print 6; + } + print 7; + } + print 8; +} + +print 9; \ No newline at end of file diff --git a/source/toy_opcodes.h b/source/toy_opcodes.h index 8f2a830..11b0d76 100644 --- a/source/toy_opcodes.h +++ b/source/toy_opcodes.h @@ -36,7 +36,8 @@ typedef enum Toy_OpcodeType { //control instructions TOY_OPCODE_RETURN, - TOY_OPCODE_JUMP, + TOY_OPCODE_JUMP, //JUMP, ADDR + TOY_OPCODE_ESCAPE, //JUMP, ADDR, UNWIND TOY_OPCODE_SCOPE_PUSH, TOY_OPCODE_SCOPE_POP, @@ -57,7 +58,7 @@ typedef enum Toy_OpcodeType { typedef enum Toy_OpParamJumpType { TOY_OP_PARAM_JUMP_ABSOLUTE = 0, //from the start of the routine's code section TOY_OP_PARAM_JUMP_RELATIVE = 1, -} Toy_OpJumpType; +} Toy_OpParamJumpType; typedef enum Toy_OpParamJumpConditional { TOY_OP_PARAM_JUMP_ALWAYS = 0, diff --git a/source/toy_routine.c b/source/toy_routine.c index 3849a1a..9542363 100644 --- a/source/toy_routine.c +++ b/source/toy_routine.c @@ -9,6 +9,30 @@ #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)) { @@ -240,7 +264,7 @@ static unsigned int writeInstructionBinaryShortCircuit(Toy_Routine** rt, Toy_Ast } //parameter address - unsigned int endAddr = SKIP_INT(rt, code); //parameter to be written later + 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); @@ -252,7 +276,7 @@ static unsigned int writeInstructionBinaryShortCircuit(Toy_Routine** rt, Toy_Ast writeRoutineCode(rt, ast.right); //set the parameter - OVERWRITE_INT(rt, code, endAddr, CURRENT_ADDRESS(rt, code) - (endAddr + 4)); + OVERWRITE_INT(rt, code, paramAddr, CURRENT_ADDRESS(rt, code) - (paramAddr + 4)); return 1; //leaves only 1 value on the stack } @@ -401,7 +425,7 @@ static unsigned int writeInstructionIfThenElse(Toy_Routine** rt, Toy_AstIfThenEl EMIT_BYTE(rt, code, TOY_OP_PARAM_JUMP_IF_FALSE); EMIT_BYTE(rt, code, 0); - unsigned int thenEndAddr = SKIP_INT(rt, code); //parameter to be written later + unsigned int thenParamAddr = SKIP_INT(rt, code); //parameter to be written later //emit then-branch writeRoutineCode(rt, ast.thenBranch); @@ -413,21 +437,21 @@ static unsigned int writeInstructionIfThenElse(Toy_Routine** rt, Toy_AstIfThenEl EMIT_BYTE(rt, code, TOY_OP_PARAM_JUMP_ALWAYS); EMIT_BYTE(rt, code, 0); - unsigned int elseEndAddr = SKIP_INT(rt, code); //parameter to be written later + unsigned int elseParamAddr = SKIP_INT(rt, code); //parameter to be written later //specify the starting position for the else branch - OVERWRITE_INT(rt, code, thenEndAddr, CURRENT_ADDRESS(rt, code) - (thenEndAddr + 4)); + 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, elseEndAddr, CURRENT_ADDRESS(rt, code) - (elseEndAddr + 4)); + 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, thenEndAddr, CURRENT_ADDRESS(rt, code) - (thenEndAddr + 4)); + OVERWRITE_INT(rt, code, thenParamAddr, CURRENT_ADDRESS(rt, code) - (thenParamAddr + 4)); } return 0; @@ -446,7 +470,7 @@ static unsigned int writeInstructionWhileThen(Toy_Routine** rt, Toy_AstWhileThen EMIT_BYTE(rt, code, TOY_OP_PARAM_JUMP_IF_FALSE); EMIT_BYTE(rt, code, 0); - unsigned int endAddr = SKIP_INT(rt, code); //parameter to be written later + unsigned int paramAddr = SKIP_INT(rt, code); //parameter to be written later //emit then-branch writeRoutineCode(rt, ast.thenBranch); @@ -459,25 +483,85 @@ static unsigned int writeInstructionWhileThen(Toy_Routine** rt, Toy_AstWhileThen EMIT_INT(rt, code, beginAddr - (CURRENT_ADDRESS(rt, code) + 4)); //this sets a negative value - OVERWRITE_INT(rt, code, endAddr, CURRENT_ADDRESS(rt, code) - (endAddr + 4)); + //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, addr - (CURRENT_ADDRESS(rt, code) + 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) { - //TODO: implement break + //unused (void)ast; - fprintf(stderr, TOY_CC_ERROR "COMPILER ERROR: Keyword 'break' not yet implemented\n" TOY_CC_RESET); - (*rt)->panic = true; + + //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) { - //TODO: implement continue + //unused (void)ast; - fprintf(stderr, TOY_CC_ERROR "COMPILER ERROR: Keyword 'continue' not yet implemented\n" TOY_CC_RESET); - (*rt)->panic = true; + + //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; } @@ -700,6 +784,8 @@ static unsigned int writeRoutineCode(Toy_Routine** rt, Toy_Ast* ast) { EMIT_BYTE(rt, code, 0); EMIT_BYTE(rt, code, 0); EMIT_BYTE(rt, code, 0); + + (*rt)->currentScopeDepth++; } result += writeRoutineCode(rt, ast->block.child); @@ -710,6 +796,8 @@ static unsigned int writeRoutineCode(Toy_Routine** rt, Toy_Ast* ast) { EMIT_BYTE(rt, code, 0); EMIT_BYTE(rt, code, 0); EMIT_BYTE(rt, code, 0); + + (*rt)->currentScopeDepth--; } break; @@ -909,12 +997,19 @@ void* Toy_compileRoutine(Toy_Ast* ast) { 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 the temp object + //cleanup + Toy_private_resizeEscapeArray(rt.breakEscapes, 0); + Toy_private_resizeEscapeArray(rt.continueEscapes, 0); + free(rt.param); free(rt.code); free(rt.jumps); diff --git a/source/toy_routine.h b/source/toy_routine.h index ca3ff0b..5723a72 100644 --- a/source/toy_routine.h +++ b/source/toy_routine.h @@ -3,6 +3,29 @@ #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 +typedef struct Toy_private_EscapeEntry_t { + unsigned int addr; //the address to write *to* + unsigned int depth; //the current depth +} Toy_private_EscapeEntry_t; + +typedef struct Toy_private_EscapeArray { + unsigned int capacity; + unsigned int count; + Toy_private_EscapeEntry_t data[]; +} Toy_private_EscapeArray; + +//not needed at runtime, so they can be bigger +#ifndef TOY_ESCAPE_INITIAL_CAPACITY +#define TOY_ESCAPE_INITIAL_CAPACITY 32 +#endif + +#ifndef TOY_ESCAPE_EXPANSION_RATE +#define TOY_ESCAPE_EXPANSION_RATE 4 +#endif + +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?) @@ -25,8 +48,11 @@ typedef struct Toy_Routine { unsigned int subsCapacity; unsigned int subsCount; + unsigned int currentScopeDepth; + Toy_private_EscapeArray* breakEscapes; + Toy_private_EscapeArray* continueEscapes; + bool panic; //any issues found at this point are compilation errors } Toy_Routine; TOY_API void* Toy_compileRoutine(Toy_Ast* ast); - diff --git a/source/toy_token_types.h b/source/toy_token_types.h index ce9b159..cf31b15 100644 --- a/source/toy_token_types.h +++ b/source/toy_token_types.h @@ -84,22 +84,22 @@ typedef enum Toy_TokenType { TOY_TOKEN_OPERATOR_BRACE_RIGHT, //other operators - TOY_TOKEN_OPERATOR_AND, - TOY_TOKEN_OPERATOR_OR, - TOY_TOKEN_OPERATOR_NEGATE, - TOY_TOKEN_OPERATOR_QUESTION, - TOY_TOKEN_OPERATOR_COLON, + TOY_TOKEN_OPERATOR_AND, // && + TOY_TOKEN_OPERATOR_OR, // || + TOY_TOKEN_OPERATOR_NEGATE, // ! + TOY_TOKEN_OPERATOR_QUESTION, // ? + TOY_TOKEN_OPERATOR_COLON, // : - TOY_TOKEN_OPERATOR_SEMICOLON, - TOY_TOKEN_OPERATOR_COMMA, + TOY_TOKEN_OPERATOR_SEMICOLON, // ; + TOY_TOKEN_OPERATOR_COMMA, // , - TOY_TOKEN_OPERATOR_DOT, // . - TOY_TOKEN_OPERATOR_CONCAT, // .. - TOY_TOKEN_OPERATOR_REST, // ... + TOY_TOKEN_OPERATOR_DOT, // . + TOY_TOKEN_OPERATOR_CONCAT, // .. + TOY_TOKEN_OPERATOR_REST, // ... //unused operators TOY_TOKEN_OPERATOR_AMPERSAND, // & - TOY_TOKEN_OPERATOR_PIPE, // | + TOY_TOKEN_OPERATOR_PIPE, // | //meta tokens TOY_TOKEN_PASS, diff --git a/source/toy_vm.c b/source/toy_vm.c index 80440fc..0d4cf2e 100644 --- a/source/toy_vm.c +++ b/source/toy_vm.c @@ -63,7 +63,7 @@ static void processRead(Toy_VM* vm) { case TOY_VALUE_STRING: { enum Toy_StringType stringType = READ_BYTE(vm); - int len = (int)READ_BYTE(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))); @@ -524,7 +524,7 @@ static void processLogical(Toy_VM* vm, Toy_OpcodeType opcode) { } static void processJump(Toy_VM* vm) { - Toy_OpJumpType type = READ_BYTE(vm); + Toy_OpParamJumpType type = READ_BYTE(vm); Toy_OpParamJumpConditional cond = READ_BYTE(vm); fixAlignment(vm); @@ -571,6 +571,19 @@ static void processJump(Toy_VM* vm) { } } +static void processEscape(Toy_VM* vm) { + fixAlignment(vm); + + int addr = READ_INT(vm); //where to go + int diff = READ_INT(vm); //what to do + + vm->programCounter += addr; + + while (diff > 0 && vm->scope != NULL) { + vm->scope = Toy_popScope(vm->scope); + } +} + static void processAssert(Toy_VM* vm) { unsigned int count = READ_BYTE(vm); @@ -881,6 +894,10 @@ static void process(Toy_VM* vm) { processJump(vm); break; + case TOY_OPCODE_ESCAPE: + processEscape(vm); + break; + case TOY_OPCODE_SCOPE_PUSH: vm->scope = Toy_pushScope(&vm->scopeBucket, vm->scope); break;