From 256538e1f9451da34f792496d07c400ea25fc5c4 Mon Sep 17 00:00:00 2001 From: Kayne Ruse Date: Sat, 1 Oct 2022 13:51:40 +0100 Subject: [PATCH] Added a utility to call toy functions from C --- scripts/test/call-from-host.toy | 23 +++ source/interpreter.c | 74 +++++--- source/interpreter.h | 3 + test/test_call_from_host.c | 312 ++++++++++++++++++++++++++++++++ test/test_interpreter.c | 2 +- 5 files changed, 390 insertions(+), 24 deletions(-) create mode 100644 scripts/test/call-from-host.toy create mode 100644 test/test_call_from_host.c diff --git a/scripts/test/call-from-host.toy b/scripts/test/call-from-host.toy new file mode 100644 index 0000000..49a8abc --- /dev/null +++ b/scripts/test/call-from-host.toy @@ -0,0 +1,23 @@ +//create a bunch of toy functions as literals to be called from C + +fn answer() { + return 42; +} + +fn identity(x) { + return x; +} + +fn makeCounter() { + var total = 0; + + fn counter() { + return ++total; + } + + return counter; +} + +fn fail() { + assert false, "Failed correctly"; +} diff --git a/source/interpreter.c b/source/interpreter.c index 928c256..1248ec7 100644 --- a/source/interpreter.c +++ b/source/interpreter.c @@ -1189,8 +1189,27 @@ static bool execFnCall(Interpreter* interpreter, bool looseFirstArgument) { return false; } + bool ret = callLiteralFn(interpreter, func, &arguments, &interpreter->stack); + + if (!ret) { + interpreter->errorOutput("Error encountered in function \""); + printLiteralCustom(identifier, interpreter->errorOutput); + interpreter->errorOutput("\"\n"); + } + + freeLiteralArray(&arguments); + freeLiteral(func); freeLiteral(identifier); + return ret; +} + +bool callLiteralFn(Interpreter* interpreter, Literal func, LiteralArray* arguments, LiteralArray* returns) { + if (!IS_FUNCTION(func)) { + interpreter->errorOutput("Function required in callLiteralFn()\n"); + return false; + } + //set up a new interpreter Interpreter inner; @@ -1225,13 +1244,10 @@ static bool execFnCall(Interpreter* interpreter, bool looseFirstArgument) { } //check the param total is correct - if ((IS_NULL(restParam) && paramArray->count != arguments.count * 2) || (!IS_NULL(restParam) && paramArray->count -2 > arguments.count * 2)) { - interpreter->errorOutput("Incorrect number of arguments passed to function \""); - printLiteralCustom(identifier, interpreter->errorOutput); - interpreter->errorOutput("\"\n"); + if ((IS_NULL(restParam) && paramArray->count != arguments->count * 2) || (!IS_NULL(restParam) && paramArray->count -2 > arguments->count * 2)) { + interpreter->errorOutput("Incorrect number of arguments passed to a function\n"); //free, and skip out - freeLiteralArray(&arguments); popScope(inner.scope); freeLiteralArray(&inner.stack); @@ -1247,7 +1263,6 @@ static bool execFnCall(Interpreter* interpreter, bool looseFirstArgument) { interpreter->errorOutput("[internal] Could not re-declare parameter\n"); //free, and skip out - freeLiteralArray(&arguments); popScope(inner.scope); freeLiteralArray(&inner.stack); @@ -1256,7 +1271,7 @@ static bool execFnCall(Interpreter* interpreter, bool looseFirstArgument) { return false; } - Literal arg = popLiteralArray(&arguments); + Literal arg = popLiteralArray(arguments); if (IS_IDENTIFIER(arg)) { Literal idn = arg; @@ -1269,10 +1284,8 @@ static bool execFnCall(Interpreter* interpreter, bool looseFirstArgument) { //free, and skip out freeLiteral(arg); - freeLiteralArray(&arguments); popScope(inner.scope); - freeLiteralArray(&inner.stack); freeLiteralArray(&inner.literalCache); @@ -1286,8 +1299,8 @@ static bool execFnCall(Interpreter* interpreter, bool looseFirstArgument) { LiteralArray rest; initLiteralArray(&rest); - while (arguments.count > 0) { - Literal lit = popLiteralArray(&arguments); + while (arguments->count > 0) { + Literal lit = popLiteralArray(arguments); pushLiteralArray(&rest, lit); freeLiteral(lit); } @@ -1303,7 +1316,6 @@ static bool execFnCall(Interpreter* interpreter, bool looseFirstArgument) { //free, and skip out freeLiteral(restType); freeLiteralArray(&rest); - freeLiteralArray(&arguments); popScope(inner.scope); freeLiteralArray(&inner.stack); @@ -1319,7 +1331,6 @@ static bool execFnCall(Interpreter* interpreter, bool looseFirstArgument) { //free, and skip out freeLiteral(restType); freeLiteral(lit); - freeLiteralArray(&arguments); popScope(inner.scope); freeLiteralArray(&inner.stack); @@ -1339,27 +1350,27 @@ static bool execFnCall(Interpreter* interpreter, bool looseFirstArgument) { interpreter->panic = inner.panic; //accept the stack as the results - LiteralArray returns; - initLiteralArray(&returns); + LiteralArray returnsFromInner; + initLiteralArray(&returnsFromInner); //unpack the results for (int i = 0; i < (returnArray->count || 1); i++) { Literal lit = popLiteralArray(&inner.stack); - pushLiteralArray(&returns, lit); //NOTE: also reverses the order + pushLiteralArray(&returnsFromInner, lit); //NOTE: also reverses the order freeLiteral(lit); } bool returnValue = true; //TODO: remove this when multiple assignment is enabled - note the BUGFIX that balances the stack - if (returns.count > 1) { + if (returnsFromInner.count > 1) { interpreter->errorOutput("Too many values returned (multiple returns not yet supported)\n"); returnValue = false; } - for (int i = 0; i < returns.count && returnValue; i++) { - Literal ret = popLiteralArray(&returns); + for (int i = 0; i < returnsFromInner.count && returnValue; i++) { + Literal ret = popLiteralArray(&returnsFromInner); //check the return types if (returnArray->count > 0 && AS_TYPE(returnArray->literals[i]).typeOf != ret.type) { @@ -1370,7 +1381,7 @@ static bool execFnCall(Interpreter* interpreter, bool looseFirstArgument) { break; } - pushLiteralArray(&interpreter->stack, ret); //NOTE: reverses again + pushLiteralArray(returns, ret); //NOTE: reverses again freeLiteral(ret); } @@ -1392,16 +1403,33 @@ static bool execFnCall(Interpreter* interpreter, bool looseFirstArgument) { inner.scope = popScope(inner.scope); } - freeLiteralArray(&returns); - freeLiteralArray(&arguments); + freeLiteralArray(&returnsFromInner); freeLiteralArray(&inner.stack); freeLiteralArray(&inner.literalCache); - freeLiteral(func); //actual bytecode persists until next call return true; } +bool callFn(Interpreter* interpreter, char* name, LiteralArray* arguments, LiteralArray* returns) { + Literal key = TO_IDENTIFIER_LITERAL(copyString(name, strlen(name)), strlen(name)); + Literal val = TO_NULL_LITERAL; + + if (!isDelcaredScopeVariable(interpreter->scope, key)) { + interpreter->errorOutput("No function with that name\n"); + return false; + } + + getScopeVariable(interpreter->scope, key, &val); + + bool ret = callLiteralFn(interpreter, val, arguments, returns); + + freeLiteral(key); + freeLiteral(val); + + return ret; +} + static bool execFnReturn(Interpreter* interpreter) { LiteralArray returns; initLiteralArray(&returns); diff --git a/source/interpreter.h b/source/interpreter.h index f01070d..5b65dd1 100644 --- a/source/interpreter.h +++ b/source/interpreter.h @@ -41,6 +41,9 @@ TOY_API bool injectNativeFn(Interpreter* interpreter, char* name, NativeFn func) typedef int (*HookFn)(Interpreter* interpreter, Literal identifier, Literal alias); TOY_API bool injectNativeHook(Interpreter* interpreter, char* name, HookFn hook); +TOY_API bool callLiteralFn(Interpreter* interpreter, Literal func, LiteralArray* arguments, LiteralArray* returns); +TOY_API bool callFn(Interpreter* interpreter, char* name, LiteralArray* arguments, LiteralArray* returns); + //utilities for the host program TOY_API bool parseIdentifierToValue(Interpreter* interpreter, Literal* literalPtr); TOY_API void setInterpreterPrint(Interpreter* interpreter, PrintFn printOutput); diff --git a/test/test_call_from_host.c b/test/test_call_from_host.c new file mode 100644 index 0000000..558016a --- /dev/null +++ b/test/test_call_from_host.c @@ -0,0 +1,312 @@ +#include "lexer.h" +#include "parser.h" +#include "compiler.h" +#include "interpreter.h" + +#include "console_colors.h" + +#include "memory.h" + +#include +#include +#include +#include + +//supress the print output +static void noPrintFn(const char* output) { + //NO OP +} + +//compilation functions +char* readFile(char* path, size_t* fileSize) { + FILE* file = fopen(path, "rb"); + + if (file == NULL) { + fprintf(stderr, ERROR "Could not open file \"%s\"\n" RESET, path); + exit(-1); + } + + fseek(file, 0L, SEEK_END); + *fileSize = ftell(file); + rewind(file); + + char* buffer = (char*)malloc(*fileSize + 1); + + if (buffer == NULL) { + fprintf(stderr, ERROR "Not enough memory to read \"%s\"\n" RESET, path); + exit(-1); + } + + size_t bytesRead = fread(buffer, sizeof(char), *fileSize, file); + + buffer[*fileSize] = '\0'; //NOTE: fread doesn't append this + + if (bytesRead < *fileSize) { + fprintf(stderr, ERROR "Could not read file \"%s\"\n" RESET, path); + exit(-1); + } + + fclose(file); + + return buffer; +} + +unsigned char* compileString(char* source, size_t* size) { + Lexer lexer; + Parser parser; + Compiler compiler; + + initLexer(&lexer, source); + initParser(&parser, &lexer); + initCompiler(&compiler); + + //run the parser until the end of the source + ASTNode* node = scanParser(&parser); + while(node != NULL) { + //pack up and leave + if (node->type == AST_NODEERROR) { + printf(ERROR "error node detected\n" RESET); + freeNode(node); + freeCompiler(&compiler); + freeParser(&parser); + return NULL; + } + + writeCompiler(&compiler, node); + freeNode(node); + node = scanParser(&parser); + } + + //get the bytecode dump + unsigned char* tb = collateCompiler(&compiler, (int*)(size)); + + //cleanup + freeCompiler(&compiler); + freeParser(&parser); + //no lexer to clean up + + //finally + return tb; +} + +void error(char* msg) { + printf(msg); + exit(-1); +} + +int main() { + { + size_t size = 0; + char* source = readFile("../scripts//test/call-from-host.toy", &size); + unsigned char* tb = compileString(source, &size); + + if (!tb) { + return -1; + } + + Interpreter interpreter; + initInterpreter(&interpreter); + runInterpreter(&interpreter, tb, size); + + //test answer + { + interpreter.printOutput("Testing answer"); + + LiteralArray arguments; + initLiteralArray(&arguments); + LiteralArray returns; + initLiteralArray(&returns); + + callFn(&interpreter, "answer", &arguments, &returns); + + //check the results + if (arguments.count != 0) { + error("Arguments has the wrong number of members\n"); + } + + if (returns.count != 1) { + error("Returns has the wrong number of members\n"); + } + + if (!IS_INTEGER(returns.literals[0]) || AS_INTEGER(returns.literals[0]) != 42) { + error("Returned value is incorrect\n"); + } + + freeLiteralArray(&arguments); + freeLiteralArray(&returns); + } + + //test identity + { + interpreter.printOutput("Testing identity"); + + LiteralArray arguments; + initLiteralArray(&arguments); + LiteralArray returns; + initLiteralArray(&returns); + + //push an argument + float pi = 3.14; + Literal arg = TO_FLOAT_LITERAL(pi); + pushLiteralArray(&arguments, arg); + + callFn(&interpreter, "identity", &arguments, &returns); + + //check the results + if (arguments.count != 0) { + error("Arguments has the wrong number of members\n"); + } + + if (returns.count != 1) { + error("Returns has the wrong number of members\n"); + } + + float epsilon = 0.1; //because floats are evil + + if (!IS_FLOAT(returns.literals[0]) || fabs(AS_FLOAT(returns.literals[0]) - pi) > epsilon) { + error("Returned value is incorrect\n"); + } + + freeLiteralArray(&arguments); + freeLiteralArray(&returns); + } + + //test makeCounter (closures) + { + interpreter.printOutput("Testing makeCounter (closures)"); + + LiteralArray arguments; + initLiteralArray(&arguments); + LiteralArray returns; + initLiteralArray(&returns); + + callFn(&interpreter, "makeCounter", &arguments, &returns); + + //check the results + if (arguments.count != 0) { + error("Arguments has the wrong number of members\n"); + } + + if (returns.count != 1) { + error("Returns has the wrong number of members\n"); + } + + //grab the resulting literal + Literal counter = popLiteralArray(&returns); + + freeLiteralArray(&arguments); + freeLiteralArray(&returns); + + //call counter repeatedly + { + LiteralArray arguments; + initLiteralArray(&arguments); + LiteralArray returns; + initLiteralArray(&returns); + + callLiteralFn(&interpreter, counter, &arguments, &returns); + + //check the results + if (arguments.count != 0) { + error("Arguments (1) has the wrong number of members\n"); + } + + if (returns.count != 1) { + error("Returns (1) has the wrong number of members\n"); + } + + if (!IS_INTEGER(returns.literals[0]) || AS_INTEGER(returns.literals[0]) != 1) { + error("Returned value (1) is incorrect\n"); + } + + freeLiteralArray(&arguments); + freeLiteralArray(&returns); + } + + { + LiteralArray arguments; + initLiteralArray(&arguments); + LiteralArray returns; + initLiteralArray(&returns); + + callLiteralFn(&interpreter, counter, &arguments, &returns); + + //check the results + if (arguments.count != 0) { + error("Arguments (2) has the wrong number of members\n"); + } + + if (returns.count != 1) { + error("Returns (2) has the wrong number of members\n"); + } + + if (!IS_INTEGER(returns.literals[0]) || AS_INTEGER(returns.literals[0]) != 2) { + error("Returned value (2) is incorrect\n"); + } + + freeLiteralArray(&arguments); + freeLiteralArray(&returns); + } + + { + LiteralArray arguments; + initLiteralArray(&arguments); + LiteralArray returns; + initLiteralArray(&returns); + + callLiteralFn(&interpreter, counter, &arguments, &returns); + + //check the results + if (arguments.count != 0) { + error("Arguments (3) has the wrong number of members\n"); + } + + if (returns.count != 1) { + error("Returns (3) has the wrong number of members\n"); + } + + if (!IS_INTEGER(returns.literals[0]) || AS_INTEGER(returns.literals[0]) != 3) { + error("Returned value (3) is incorrect\n"); + } + + freeLiteralArray(&arguments); + freeLiteralArray(&returns); + } + } + + //test assertion failure + { + interpreter.printOutput("Testing assertion failure"); + + LiteralArray arguments; + initLiteralArray(&arguments); + LiteralArray returns; + initLiteralArray(&returns); + + bool ret = callFn(&interpreter, "fail", &arguments, &returns); + + //check the results + if (arguments.count != 0) { + error("Arguments has the wrong number of members\n"); + } + + if (returns.count != 1 || !IS_NULL(returns.literals[0])) { + error("Returns has the wrong number of members\n"); + } + + if (!ret) { + error("Assertion gives the wrong return value\n"); + } + + freeLiteralArray(&arguments); + freeLiteralArray(&returns); + } + + //clean up + freeInterpreter(&interpreter); + } + + printf(NOTICE "All good\n" RESET); + return 0; +} + diff --git a/test/test_interpreter.c b/test/test_interpreter.c index 9a87bb4..8fdf90c 100644 --- a/test/test_interpreter.c +++ b/test/test_interpreter.c @@ -92,7 +92,7 @@ void runBinary(unsigned char* tb, size_t size) { Interpreter interpreter; initInterpreter(&interpreter); - //NOTE: supress print output for testing + //NOTE: suppress print output for testing setInterpreterPrint(&interpreter, noPrintFn); runInterpreter(&interpreter, tb, size);