diff --git a/scripts/small.toy b/scripts/small.toy index e592fc3..1a1ab9c 100644 --- a/scripts/small.toy +++ b/scripts/small.toy @@ -1,8 +1,19 @@ +fn capture(count: int) { + print count; + print capture; + if (count < 5) { + return count; + } + print count; + print capture; -fn foo() { - // + count++; + count++; + +// return capture(count + 1); + return -1; } -foo(); +print capture(52); diff --git a/scripts/test/jumps-in-functions.toy b/scripts/test/jumps-in-functions.toy new file mode 100644 index 0000000..6905259 --- /dev/null +++ b/scripts/test/jumps-in-functions.toy @@ -0,0 +1,87 @@ +fn sanity() { + //test true jump + if (true) { + assert true, "if-then failed (1)"; + } + else { + assert false, "if-then failed (2)"; + } + + + //test false jump + if (false) { + assert false, "if-then failed (3)"; + } + else { + assert true, "if-then failed (4)"; + } + + + //test while loop + var whileCounter = 0; + while (whileCounter < 10) { + whileCounter = whileCounter + 1; + } + + assert whileCounter == 10, "while-loop failed"; + + + //test for loop + var forCache = 0; + for (var i = 0; i < 20; i++) { + forCache = i; + } + + assert forCache == 19, "for-loop failed"; + + + //test break - while + var breakWhileCache = 0; + while(true) { + breakWhileCache = breakWhileCache + 1; + + if (breakWhileCache >= 7) { + break; + } + } + + assert breakWhileCache == 7, "break-while failed"; + + + //test continue - while + var continueWhileCache = 0; + while (continueWhileCache < 10) { + continueWhileCache = continueWhileCache + 1; + + if (continueWhileCache >= 7) { + continue; + } + + assert continueWhileCache < 7, "continue-while failed"; + } + + + //test break - for + for (var i = 0; i < 10; i++) { + if (i >= 7) { + break; + } + + assert i < 7, "break-for failed"; + } + + + //test break - continue + for (var i = 0; i < 10; i++) { + if (i >= 7) { + continue; + } + + assert i < 7, "continue-for failed"; + } + + print "All good"; +} + +//invoke the test +sanity(); diff --git a/source/compiler.c b/source/compiler.c index cbeb60d..4bc01fb 100644 --- a/source/compiler.c +++ b/source/compiler.c @@ -267,7 +267,7 @@ static int writeLiteralToCompiler(Compiler* compiler, Literal literal) { return index; } -static void writeCompilerWithJumps(Compiler* compiler, Node* node, void* breakAddressesPtr, void* continueAddressesPtr) { +static void writeCompilerWithJumps(Compiler* compiler, Node* node, void* breakAddressesPtr, void* continueAddressesPtr, int jumpOffsets) { //NOTE: jumpOfsets are included, because function arg and return indexes are embedded in the code body i.e. need to include thier sizes in the jump //grow if the bytecode space is too small if (compiler->count + 32 > compiler->capacity) { int oldCapacity = compiler->capacity; @@ -291,20 +291,20 @@ static void writeCompilerWithJumps(Compiler* compiler, Node* node, void* breakAd case NODE_UNARY: //pass to the child node, then embed the unary command (print, negate, etc.) - writeCompilerWithJumps(compiler, node->unary.child, breakAddressesPtr, continueAddressesPtr); + writeCompilerWithJumps(compiler, node->unary.child, breakAddressesPtr, continueAddressesPtr, jumpOffsets); compiler->bytecode[compiler->count++] = (unsigned char)node->unary.opcode; //1 byte break; case NODE_BINARY: //pass to the child nodes, then embed the binary command (math, etc.) - writeCompilerWithJumps(compiler, node->binary.left, breakAddressesPtr, continueAddressesPtr); - writeCompilerWithJumps(compiler, node->binary.right, breakAddressesPtr, continueAddressesPtr); + writeCompilerWithJumps(compiler, node->binary.left, breakAddressesPtr, continueAddressesPtr, jumpOffsets); + writeCompilerWithJumps(compiler, node->binary.right, breakAddressesPtr, continueAddressesPtr, jumpOffsets); compiler->bytecode[compiler->count++] = (unsigned char)node->binary.opcode; //1 byte break; case NODE_GROUPING: compiler->bytecode[compiler->count++] = (unsigned char)OP_GROUPING_BEGIN; //1 byte - writeCompilerWithJumps(compiler, node->grouping.child, breakAddressesPtr, continueAddressesPtr); + writeCompilerWithJumps(compiler, node->grouping.child, breakAddressesPtr, continueAddressesPtr, jumpOffsets); compiler->bytecode[compiler->count++] = (unsigned char)OP_GROUPING_END; //1 byte break; @@ -312,7 +312,7 @@ static void writeCompilerWithJumps(Compiler* compiler, Node* node, void* breakAd compiler->bytecode[compiler->count++] = (unsigned char)OP_SCOPE_BEGIN; //1 byte for (int i = 0; i < node->block.count; i++) { - writeCompilerWithJumps(compiler, &(node->block.nodes[i]), breakAddressesPtr, continueAddressesPtr); + writeCompilerWithJumps(compiler, &(node->block.nodes[i]), breakAddressesPtr, continueAddressesPtr, jumpOffsets); } compiler->bytecode[compiler->count++] = (unsigned char)OP_SCOPE_END; //1 byte @@ -343,7 +343,7 @@ static void writeCompilerWithJumps(Compiler* compiler, Node* node, void* breakAd case NODE_VAR_DECL: { //first, embed the expression (leaves it on the stack) - writeCompilerWithJumps(compiler, node->varDecl.expression, breakAddressesPtr, continueAddressesPtr); + writeCompilerWithJumps(compiler, node->varDecl.expression, breakAddressesPtr, continueAddressesPtr, jumpOffsets); //write each piece of the declaration to the bytecode int identifierIndex = findLiteralIndex(&compiler->literalCache, node->varDecl.identifier); @@ -379,7 +379,7 @@ static void writeCompilerWithJumps(Compiler* compiler, Node* node, void* breakAd initCompiler(fnCompiler); writeCompiler(fnCompiler, node->fnDecl.arguments); //can be empty, but not NULL writeCompiler(fnCompiler, node->fnDecl.returns); //can be empty, but not NULL - writeCompiler(fnCompiler, node->fnDecl.block); //can be empty, but not NULL + writeCompilerWithJumps(fnCompiler, node->fnDecl.block, NULL, NULL, -4); //can be empty, but not NULL //create the function in the literal cache (by storing the compiler object) Literal fnLiteral = TO_FUNCTION_LITERAL(fnCompiler, 0); @@ -429,7 +429,7 @@ static void writeCompilerWithJumps(Compiler* compiler, Node* node, void* breakAd for (int i = 0; i < node->fnCall.arguments->fnCollection.count; i++) { //reverse order, to count from the beginning in the interpreter //sub-calls if (node->fnCall.arguments->fnCollection.nodes[i].type != NODE_LITERAL) { - writeCompilerWithJumps(compiler, &node->fnCall.arguments->fnCollection.nodes[i], breakAddressesPtr, continueAddressesPtr); + writeCompilerWithJumps(compiler, &node->fnCall.arguments->fnCollection.nodes[i], breakAddressesPtr, continueAddressesPtr, jumpOffsets); continue; } @@ -482,7 +482,7 @@ static void writeCompilerWithJumps(Compiler* compiler, Node* node, void* breakAd case NODE_PATH_IF: { //process the condition - writeCompilerWithJumps(compiler, node->path.condition, breakAddressesPtr, continueAddressesPtr); + writeCompilerWithJumps(compiler, node->path.condition, breakAddressesPtr, continueAddressesPtr, jumpOffsets); //cache the point to insert the jump distance at compiler->bytecode[compiler->count++] = OP_IF_FALSE_JUMP; //1 byte @@ -490,7 +490,7 @@ static void writeCompilerWithJumps(Compiler* compiler, Node* node, void* breakAd compiler->count += sizeof(unsigned short); //2 bytes //write the then path - writeCompilerWithJumps(compiler, node->path.thenPath, breakAddressesPtr, continueAddressesPtr); + writeCompilerWithJumps(compiler, node->path.thenPath, breakAddressesPtr, continueAddressesPtr, jumpOffsets); int jumpToEnd = 0; @@ -502,14 +502,14 @@ static void writeCompilerWithJumps(Compiler* compiler, Node* node, void* breakAd } //update the jumpToElse to point here - AS_USHORT(compiler->bytecode[jumpToElse]) = compiler->count; //2 bytes + AS_USHORT(compiler->bytecode[jumpToElse]) = compiler->count + jumpOffsets; //2 bytes if (node->path.elsePath) { //if there's an else path, write it and - writeCompilerWithJumps(compiler, node->path.elsePath, breakAddressesPtr, continueAddressesPtr); + writeCompilerWithJumps(compiler, node->path.elsePath, breakAddressesPtr, continueAddressesPtr, jumpOffsets); //update the jumpToEnd to point here - AS_USHORT(compiler->bytecode[jumpToEnd]) = compiler->count; //2 bytes + AS_USHORT(compiler->bytecode[jumpToEnd]) = compiler->count + jumpOffsets; //2 bytes } } break; @@ -526,7 +526,7 @@ static void writeCompilerWithJumps(Compiler* compiler, Node* node, void* breakAd unsigned short jumpToStart = compiler->count; //process the condition - writeCompilerWithJumps(compiler, node->path.condition, &breakAddresses, &continueAddresses); + writeCompilerWithJumps(compiler, node->path.condition, &breakAddresses, &continueAddresses, jumpOffsets); //if false, jump to end compiler->bytecode[compiler->count++] = OP_IF_FALSE_JUMP; //1 byte @@ -534,27 +534,30 @@ static void writeCompilerWithJumps(Compiler* compiler, Node* node, void* breakAd compiler->count += sizeof(unsigned short); //2 bytes //write the body - writeCompilerWithJumps(compiler, node->path.thenPath, &breakAddresses, &continueAddresses); + writeCompilerWithJumps(compiler, node->path.thenPath, &breakAddresses, &continueAddresses, jumpOffsets); //jump to condition compiler->bytecode[compiler->count++] = OP_JUMP; //1 byte - AS_USHORT(compiler->bytecode[compiler->count]) = jumpToStart; + AS_USHORT(compiler->bytecode[compiler->count]) = jumpToStart + jumpOffsets; compiler->count += sizeof(unsigned short); //2 bytes //jump from condition - AS_USHORT(compiler->bytecode[jumpToEnd]) = (unsigned short)compiler->count; + AS_USHORT(compiler->bytecode[jumpToEnd]) = (unsigned short)compiler->count + jumpOffsets; //set the breaks and continues for (int i = 0; i < breakAddresses.count; i++) { int point = AS_INTEGER(breakAddresses.literals[i]); - AS_USHORT(compiler->bytecode[point]) = (unsigned short)compiler->count; + AS_USHORT(compiler->bytecode[point]) = (unsigned short)compiler->count + jumpOffsets; } for (int i = 0; i < continueAddresses.count; i++) { int point = AS_INTEGER(continueAddresses.literals[i]); - AS_USHORT(compiler->bytecode[point]) = jumpToStart; + AS_USHORT(compiler->bytecode[point]) = jumpToStart + jumpOffsets; } + //clear the stack after use + compiler->bytecode[compiler->count++] = OP_POP_STACK; //1 byte + //cleanup freeLiteralArray(&breakAddresses); freeLiteralArray(&continueAddresses); @@ -572,11 +575,11 @@ static void writeCompilerWithJumps(Compiler* compiler, Node* node, void* breakAd compiler->bytecode[compiler->count++] = OP_SCOPE_BEGIN; //1 byte //initial setup - writeCompilerWithJumps(compiler, node->path.preClause, &breakAddresses, &continueAddresses); + writeCompilerWithJumps(compiler, node->path.preClause, &breakAddresses, &continueAddresses, jumpOffsets); //conditional unsigned short jumpToStart = compiler->count; - writeCompilerWithJumps(compiler, node->path.condition, &breakAddresses, &continueAddresses); + writeCompilerWithJumps(compiler, node->path.condition, &breakAddresses, &continueAddresses, jumpOffsets); //if false jump to end compiler->bytecode[compiler->count++] = OP_IF_FALSE_JUMP; //1 byte @@ -585,34 +588,37 @@ static void writeCompilerWithJumps(Compiler* compiler, Node* node, void* breakAd //write the body compiler->bytecode[compiler->count++] = OP_SCOPE_BEGIN; //1 byte - writeCompilerWithJumps(compiler, node->path.thenPath, &breakAddresses, &continueAddresses); + writeCompilerWithJumps(compiler, node->path.thenPath, &breakAddresses, &continueAddresses, jumpOffsets); compiler->bytecode[compiler->count++] = OP_SCOPE_END; //1 byte //for-breaks actually jump to the bottom int jumpToIncrement = compiler->count; //evaluate third clause, restart - writeCompilerWithJumps(compiler, node->path.postClause, &breakAddresses, &continueAddresses); + writeCompilerWithJumps(compiler, node->path.postClause, &breakAddresses, &continueAddresses, jumpOffsets); compiler->bytecode[compiler->count++] = OP_JUMP; //1 byte - AS_USHORT(compiler->bytecode[compiler->count]) = jumpToStart; + AS_USHORT(compiler->bytecode[compiler->count]) = jumpToStart + jumpOffsets; compiler->count += sizeof(unsigned short); //2 bytes - AS_USHORT(compiler->bytecode[jumpToEnd]) = compiler->count; + AS_USHORT(compiler->bytecode[jumpToEnd]) = compiler->count + jumpOffsets; compiler->bytecode[compiler->count++] = OP_SCOPE_END; //1 byte //set the breaks and continues for (int i = 0; i < breakAddresses.count; i++) { int point = AS_INTEGER(breakAddresses.literals[i]); - AS_USHORT(compiler->bytecode[point]) = compiler->count; + AS_USHORT(compiler->bytecode[point]) = compiler->count + jumpOffsets; } for (int i = 0; i < continueAddresses.count; i++) { int point = AS_INTEGER(continueAddresses.literals[i]); - AS_USHORT(compiler->bytecode[point]) = jumpToIncrement; + AS_USHORT(compiler->bytecode[point]) = jumpToIncrement + jumpOffsets; } + //clear the stack after use + compiler->bytecode[compiler->count++] = OP_POP_STACK; //1 byte + //cleanup freeLiteralArray(&breakAddresses); freeLiteralArray(&continueAddresses); @@ -658,7 +664,7 @@ static void writeCompilerWithJumps(Compiler* compiler, Node* node, void* breakAd case NODE_PATH_RETURN: { //read each returned literal onto the stack, and return the number of values to return for (int i = 0; i < node->path.thenPath->fnCollection.count; i++) { - writeCompilerWithJumps(compiler, &node->path.thenPath->fnCollection.nodes[i], breakAddressesPtr, continueAddressesPtr); + writeCompilerWithJumps(compiler, &node->path.thenPath->fnCollection.nodes[i], breakAddressesPtr, continueAddressesPtr, jumpOffsets); } //push the return, with the number of literals @@ -714,7 +720,7 @@ static void writeCompilerWithJumps(Compiler* compiler, Node* node, void* breakAd } void writeCompiler(Compiler* compiler, Node* node) { - writeCompilerWithJumps(compiler, node, NULL, NULL); + writeCompilerWithJumps(compiler, node, NULL, NULL, 0); } void freeCompiler(Compiler* compiler) { diff --git a/source/interpreter.c b/source/interpreter.c index 2a31146..d641707 100644 --- a/source/interpreter.c +++ b/source/interpreter.c @@ -597,8 +597,12 @@ static bool execPushLiteral(Interpreter* interpreter, bool lng) { static bool rawLiteral(Interpreter* interpreter) { Literal lit = popLiteralArray(&interpreter->stack); - if (!parseIdentifierToValue(interpreter, &lit)) { - return false; + if (IS_IDENTIFIER(lit)) { + Literal idn = lit; + if (!parseIdentifierToValue(interpreter, &lit)) { + return false; + } + freeLiteral(idn); } pushLiteralArray(&interpreter->stack, lit); @@ -1277,8 +1281,12 @@ static bool execFalseJump(Interpreter* interpreter) { //actually jump Literal lit = popLiteralArray(&interpreter->stack); - if (!parseIdentifierToValue(interpreter, &lit)) { - return false; + bool freeLit = false; + if (IS_IDENTIFIER(lit)) { + Literal idn = lit; + parseIdentifierToValue(interpreter, &lit); + freeLiteral(idn); + freeLit = true; } if (IS_NULL(lit)) { @@ -1290,7 +1298,9 @@ static bool execFalseJump(Interpreter* interpreter) { interpreter->count = target + interpreter->codeStart; } - freeLiteral(lit); + if (freeLit) { + freeLiteral(lit); + } return true; } @@ -1322,8 +1332,6 @@ static bool execFnCall(Interpreter* interpreter) { return false; } - freeLiteral(identifier); - //check for side-loaded native functions if (IS_FUNCTION_NATIVE(func)) { //reverse the order to the correct order @@ -1343,6 +1351,18 @@ static bool execFnCall(Interpreter* interpreter) { return true; } + if (!IS_FUNCTION(func)) { + printf(ERROR "ERROR: Function not found: "); + printLiteral(identifier); + printf("\n" RESET); + + freeLiteral(identifier); + freeLiteralArray(&arguments); + return false; + } + + freeLiteral(identifier); + //set up a new interpreter Interpreter inner; @@ -1463,7 +1483,7 @@ static bool execFnCall(Interpreter* interpreter) { initLiteralArray(&returns); //unpack the results - while (inner.stack.count > 0) { + for (int i = 0; i < (returnArray->count || 1); i++) { Literal lit = popLiteralArray(&inner.stack); pushLiteralArray(&returns, lit); //NOTE: also reverses the order freeLiteral(lit); @@ -1473,7 +1493,7 @@ static bool execFnCall(Interpreter* interpreter) { //TODO: remove this when multiple assignment is enabled - note the BUGFIX that balances the stack if (returns.count > 1) { - printf(ERROR "ERROR: Too many values returned (multiple returns not yet implemented)\n" RESET); + printf(ERROR "ERROR: Too many values returned (multiple returns not yet supported)\n" RESET); returnValue = false; } @@ -1526,7 +1546,11 @@ static bool execFnReturn(Interpreter* interpreter) { //get the values of everything on the stack while (interpreter->stack.count > 0) { Literal lit = popLiteralArray(&interpreter->stack); - parseIdentifierToValue(interpreter, &lit); + if (IS_IDENTIFIER(lit)) { + Literal idn = lit; + parseIdentifierToValue(interpreter, &lit); + freeLiteral(idn); + } pushLiteralArray(&returns, lit); //reverses the order freeLiteral(lit); } @@ -1731,6 +1755,12 @@ static void execInterpreter(Interpreter* interpreter) { } break; + case OP_POP_STACK: + while (interpreter->stack.count > 0) { + freeLiteral(popLiteralArray(&interpreter->stack)); + } + break; + default: printf(ERROR "Error: Unknown opcode found %d, terminating\n" RESET, opcode); printLiteralArray(&interpreter->stack, "\n"); diff --git a/source/opcodes.h b/source/opcodes.h index 1189eb9..cf6724b 100644 --- a/source/opcodes.h +++ b/source/opcodes.h @@ -63,6 +63,9 @@ typedef enum Opcode { OP_FN_CALL, OP_FN_RETURN, + //pop the stack at the end of a complex statement + OP_POP_STACK, + //meta OP_FN_END, //different from SECTION_END OP_SECTION_END = 255, diff --git a/test/makefile b/test/makefile index d171f9e..9b87749 100644 --- a/test/makefile +++ b/test/makefile @@ -18,7 +18,7 @@ all: $(OBJ) $(TESTS:%.c=../$(OUTDIR)/%.exe) ifeq ($(shell uname),Linux) valgrind --leak-check=full --track-origins=yes $@ else - @echo please run these tests with valgrind on linux + $@ endif $(OBJ): | $(ODIR) diff --git a/test/test_interpreter.c b/test/test_interpreter.c index 29b95cf..d1955ce 100644 --- a/test/test_interpreter.c +++ b/test/test_interpreter.c @@ -150,13 +150,14 @@ int main() { { //run each file in ../scripts/test/ - int count = 12; + int count = 13; char* filenames[] = { "arithmetic.toy", "casting.toy", "comparisons.toy", "functions.toy", "jumps.toy", + "jumps-in-functions.toy", "logicals.toy", "long-array.toy", "long-dictionary.toy",