From 7fb9ebbce0b8bfc32ed376e1c75aaa81035c064c Mon Sep 17 00:00:00 2001 From: Kayne Ruse Date: Mon, 5 Sep 2022 06:39:05 +0100 Subject: [PATCH] Import and export are working --- docs/TODO.txt | 2 +- scripts/test/imports-and-exports.toy | 37 ++++++++ source/compiler.c | 20 +++++ source/interpreter.c | 124 +++++++++++++++++++++++++-- source/interpreter.h | 3 +- source/node.c | 16 ++++ source/node.h | 14 ++- source/opcodes.h | 3 + source/parser.c | 56 ++++++++++++ test/test_interpreter.c | 3 +- 10 files changed, 266 insertions(+), 12 deletions(-) create mode 100644 scripts/test/imports-and-exports.toy diff --git a/docs/TODO.txt b/docs/TODO.txt index 546d960..c63f697 100644 --- a/docs/TODO.txt +++ b/docs/TODO.txt @@ -30,9 +30,9 @@ DONE: Address circular references DONE: are compounds shallow or deep copies? Deep copies DONE: third output stream, for lexer/parser/compiler/interpreter errors DONE: Assertion-based test scripts +DONE: Import/export keywords -TODO: Import/export keywords TODO: slice and dot notation around the builtin _index function TODO: ternary operator TODO: Nullish types? diff --git a/scripts/test/imports-and-exports.toy b/scripts/test/imports-and-exports.toy new file mode 100644 index 0000000..05bd307 --- /dev/null +++ b/scripts/test/imports-and-exports.toy @@ -0,0 +1,37 @@ +//test basic import/export +{ + var variable: int = 42; + export variable as field; +} + +{ + import field as value; + assert value == 42, "import/export failed"; +} + +//test functions using import/export +{ + fn f() { + import field; + + assert field == 42, "import in function failed"; + } +} + +//test importing/exporting of functions +{ + fn func() { + return 69; + } + + export func; +} + +{ + import func; + assert func() == 69, "import/export of functions failed"; +} + +//TODO: test that variables retain their types with the typeOf keyword + +print "All good"; diff --git a/source/compiler.c b/source/compiler.c index 7654102..83f4124 100644 --- a/source/compiler.c +++ b/source/compiler.c @@ -716,6 +716,26 @@ static void writeCompilerWithJumps(Compiler* compiler, Node* node, void* breakAd compiler->bytecode[compiler->count++] = (unsigned char)OP_VAR_ASSIGN; //1 byte } break; + + case NODE_IMPORT: { + //push the identifier, and the alias + writeLiteralToCompiler(compiler, node->import.identifier); + writeLiteralToCompiler(compiler, node->import.alias); + + //push the import opcode + compiler->bytecode[compiler->count++] = (unsigned char)OP_IMPORT; //1 byte + } + break; + + case NODE_EXPORT: { + //push the identifier, and the alias + writeLiteralToCompiler(compiler, node->import.identifier); + writeLiteralToCompiler(compiler, node->import.alias); + + //push the import opcode + compiler->bytecode[compiler->count++] = (unsigned char)OP_EXPORT; //1 byte + } + break; } } diff --git a/source/interpreter.c b/source/interpreter.c index 591349c..d012d5a 100644 --- a/source/interpreter.c +++ b/source/interpreter.c @@ -81,6 +81,11 @@ void initInterpreter(Interpreter* interpreter) { initLiteralArray(&interpreter->stack); + interpreter->exports = ALLOCATE(LiteralDictionary, 1); + initLiteralDictionary(interpreter->exports); + interpreter->exportTypes = ALLOCATE(LiteralDictionary, 1); + initLiteralDictionary(interpreter->exportTypes); + setInterpreterPrint(interpreter, printWrapper); setInterpreterAssert(interpreter, assertWrapper); setInterpreterError(interpreter, errorWrapper); @@ -115,6 +120,11 @@ void freeInterpreter(Interpreter* interpreter) { interpreter->scope = popScope(interpreter->scope); } + FREE(LiteralDictionary, interpreter->exports); + interpreter->exports = NULL; + FREE(LiteralDictionary, interpreter->exportTypes); + interpreter->exportTypes = NULL; + freeLiteralArray(&interpreter->literalCache); freeLiteralArray(&interpreter->stack); } @@ -509,7 +519,7 @@ static bool execVarDecl(Interpreter* interpreter, bool lng) { printLiteralCustom(identifier, interpreter->errorOutput); interpreter->errorOutput("\"\n"); - freeLiteral(identifier); //TODO: test this + freeLiteral(identifier); freeLiteral(type); freeLiteral(val); @@ -1071,6 +1081,8 @@ static bool execFnCall(Interpreter* interpreter) { inner.codeStart = -1; inner.panic = false; initLiteralArray(&inner.stack); + inner.exports = interpreter->exports; + inner.exportTypes = interpreter->exportTypes; setInterpreterPrint(&inner, interpreter->printOutput); setInterpreterAssert(&inner, interpreter->assertOutput); setInterpreterError(&inner, interpreter->errorOutput); @@ -1097,7 +1109,9 @@ static bool execFnCall(Interpreter* interpreter) { //free, and skip out freeLiteralArray(&arguments); popScope(inner.scope); - freeInterpreter(&inner); + + freeLiteralArray(&inner.stack); + freeLiteralArray(&inner.literalCache); return false; } @@ -1111,7 +1125,9 @@ static bool execFnCall(Interpreter* interpreter) { //free, and skip out freeLiteralArray(&arguments); popScope(inner.scope); - freeInterpreter(&inner); + + freeLiteralArray(&inner.stack); + freeLiteralArray(&inner.literalCache); return false; } @@ -1131,7 +1147,10 @@ static bool execFnCall(Interpreter* interpreter) { freeLiteral(arg); freeLiteralArray(&arguments); popScope(inner.scope); - freeInterpreter(&inner); + + + freeLiteralArray(&inner.stack); + freeLiteralArray(&inner.literalCache); return false; } @@ -1162,7 +1181,9 @@ static bool execFnCall(Interpreter* interpreter) { freeLiteralArray(&rest); freeLiteralArray(&arguments); popScope(inner.scope); - freeInterpreter(&inner); + + freeLiteralArray(&inner.stack); + freeLiteralArray(&inner.literalCache); return false; } @@ -1176,7 +1197,9 @@ static bool execFnCall(Interpreter* interpreter) { freeLiteral(lit); freeLiteralArray(&arguments); popScope(inner.scope); - freeInterpreter(&inner); + + freeLiteralArray(&inner.stack); + freeLiteralArray(&inner.literalCache); return false; } @@ -1227,7 +1250,7 @@ static bool execFnCall(Interpreter* interpreter) { freeLiteral(ret); } - //free + //manual free //BUGFIX: handle scopes of functions, which refer to the parent scope (leaking memory) while(inner.scope != AS_FUNCTION(func).scope) { for (int i = 0; i < inner.scope->variables.capacity; i++) { @@ -1284,6 +1307,81 @@ static bool execFnReturn(Interpreter* interpreter) { return false; } +static bool execImport(Interpreter* interpreter) { + Literal alias = popLiteralArray(&interpreter->stack); + Literal identifier = popLiteralArray(&interpreter->stack); + + Literal lit = getLiteralDictionary(interpreter->exports, identifier); + Literal type = getLiteralDictionary(interpreter->exportTypes, identifier); + + //use the alias + if (!IS_NULL(alias)) { + if (!declareScopeVariable(interpreter->scope, alias, type)) { + interpreter->errorOutput("Can't redefine the variable \""); + printLiteralCustom(alias, interpreter->errorOutput); + interpreter->errorOutput("\"\n"); + + freeLiteral(lit); + freeLiteral(type); + freeLiteral(alias); + freeLiteral(identifier); + return false; + } + + setScopeVariable(interpreter->scope, alias, lit, false); + } + + //use the original identifier + else { + if (!declareScopeVariable(interpreter->scope, identifier, type)) { + interpreter->errorOutput("Can't redefine the variable \""); + printLiteralCustom(identifier, interpreter->errorOutput); + interpreter->errorOutput("\"\n"); + + freeLiteral(lit); + freeLiteral(type); + freeLiteral(alias); + freeLiteral(identifier); + return false; + } + + setScopeVariable(interpreter->scope, identifier, lit, false); + } + + //cleanup + freeLiteral(lit); + freeLiteral(type); + freeLiteral(alias); + freeLiteral(identifier); + return true; +} + +static bool execExport(Interpreter* interpreter) { + Literal alias = popLiteralArray(&interpreter->stack); + Literal identifier = popLiteralArray(&interpreter->stack); + + Literal lit = TO_NULL_LITERAL; + + getScopeVariable(interpreter->scope, identifier, &lit); + Literal type = getScopeType(interpreter->scope, identifier); + + if (!IS_NULL(alias)) { + setLiteralDictionary(interpreter->exports, alias, lit); + setLiteralDictionary(interpreter->exportTypes, alias, type); + } + else { + setLiteralDictionary(interpreter->exports, identifier, lit); + setLiteralDictionary(interpreter->exportTypes, identifier, type); + } + + //cleanup + freeLiteral(lit); + freeLiteral(type); + freeLiteral(alias); + freeLiteral(identifier); + return true; +} + //the heart of toy static void execInterpreter(Interpreter* interpreter) { //set the starting point for the interpreter @@ -1473,6 +1571,18 @@ static void execInterpreter(Interpreter* interpreter) { } break; + case OP_IMPORT: + if (!execImport(interpreter)) { + return; + } + break; + + case OP_EXPORT: + if (!execExport(interpreter)) { + return; + } + break; + case OP_POP_STACK: while (interpreter->stack.count > 0) { freeLiteral(popLiteralArray(&interpreter->stack)); diff --git a/source/interpreter.h b/source/interpreter.h index f59a895..d72bb4e 100644 --- a/source/interpreter.h +++ b/source/interpreter.h @@ -21,7 +21,8 @@ typedef struct Interpreter { LiteralArray stack; //output - // LiteralDictionary exports; //TODO: read-write - interface with Toy from C + LiteralDictionary* exports; //read-write - interface with Toy from C - this is a pointer, since it works at a script-level + LiteralDictionary* exportTypes; PrintFn printOutput; PrintFn assertOutput; PrintFn errorOutput; diff --git a/source/node.c b/source/node.c index 0ea5e38..ec3e247 100644 --- a/source/node.c +++ b/source/node.c @@ -93,6 +93,12 @@ void freeNodeCustom(Node* node, bool freeSelf) { case NODE_INCREMENT_POSTFIX: freeLiteral(node->increment.identifier); break; + + case NODE_IMPORT: + case NODE_EXPORT: + freeLiteral(node->import.identifier); + freeLiteral(node->import.alias); + break; } if (freeSelf) { @@ -246,3 +252,13 @@ void emitNodePostfixIncrement(Node** nodeHandle, Literal identifier, int increme *nodeHandle = tmp; } + +void emitNodeImport(Node** nodeHandle, NodeType mode, Literal identifier, Literal alias) { + Node* tmp = ALLOCATE(Node, 1); + + tmp->type = mode; + tmp->import.identifier = copyLiteral(identifier); + tmp->import.alias = copyLiteral(alias); + + *nodeHandle = tmp; +} \ No newline at end of file diff --git a/source/node.h b/source/node.h index 5f1e454..9025bd0 100644 --- a/source/node.h +++ b/source/node.h @@ -23,11 +23,13 @@ typedef enum NodeType { NODE_PATH_IF, //for control flow NODE_PATH_WHILE, //for control flow NODE_PATH_FOR, //for control flow - NODE_PATH_BREAK, - NODE_PATH_CONTINUE, + NODE_PATH_BREAK, //for control flow + NODE_PATH_CONTINUE, //for control flow NODE_PATH_RETURN, NODE_INCREMENT_PREFIX, NODE_INCREMENT_POSTFIX, + NODE_IMPORT, + NODE_EXPORT, } NodeType; typedef struct NodeLiteral { @@ -116,6 +118,12 @@ typedef struct NodeIncrement { int increment; } NodeIncrement; +typedef struct NodeImport { + NodeType type; + Literal identifier; + Literal alias; +} NodeImport; + union _node { NodeType type; NodeLiteral atomic; @@ -131,6 +139,7 @@ union _node { NodeFnCall fnCall; NodePath path; NodeIncrement increment; + NodeImport import; }; void freeNode(Node* node); @@ -148,3 +157,4 @@ void emitNodeFnCollection(Node** nodeHandle); void emitNodePath(Node** nodeHandle, NodeType type, Node* preClause, Node* postClause, Node* condition, Node* thenPath, Node* elsePath); void emitNodePrefixIncrement(Node** nodeHandle, Literal identifier, int increment); void emitNodePostfixIncrement(Node** nodeHandle, Literal identifier, int increment); +void emitNodeImport(Node** nodeHandle, NodeType mode, Literal identifier, Literal alias); \ No newline at end of file diff --git a/source/opcodes.h b/source/opcodes.h index cf6724b..fe5f638 100644 --- a/source/opcodes.h +++ b/source/opcodes.h @@ -44,6 +44,9 @@ typedef enum Opcode { OP_TYPE_CAST, //temporarily change a type of an atomic value + OP_IMPORT, + OP_EXPORT, + //comparion of values OP_COMPARE_EQUAL, OP_COMPARE_NOT_EQUAL, diff --git a/source/parser.c b/source/parser.c index e20e392..54a8e69 100644 --- a/source/parser.c +++ b/source/parser.c @@ -1175,6 +1175,50 @@ static void returnStmt(Parser* parser, Node** nodeHandle) { emitNodePath(nodeHandle, NODE_PATH_RETURN, NULL, NULL, NULL, returnValues, NULL); } +static void importStmt(Parser* parser, Node** nodeHandle) { + //read the identifier + Node* node = NULL; + advance(parser); + identifier(parser, &node); + Literal idn = copyLiteral(node->atomic.literal); + freeNode(node); + + Literal alias = TO_NULL_LITERAL; + + if (match(parser, TOKEN_AS)) { + advance(parser); + identifier(parser, &node); + alias = copyLiteral(node->atomic.literal); + freeNode(node); + } + + emitNodeImport(nodeHandle, NODE_IMPORT, idn, alias); + + consume(parser, TOKEN_SEMICOLON, "Expected ';' at end of import statement"); +} + +static void exportStmt(Parser* parser, Node** nodeHandle) { + //read the identifier + Node* node = NULL; + advance(parser); + identifier(parser, &node); + Literal idn = copyLiteral(node->atomic.literal); + freeNode(node); + + Literal alias = TO_NULL_LITERAL; + + if (match(parser, TOKEN_AS)) { + advance(parser); + identifier(parser, &node); + alias = copyLiteral(node->atomic.literal); + freeNode(node); + } + + emitNodeImport(nodeHandle, NODE_EXPORT, idn, alias); + + consume(parser, TOKEN_SEMICOLON, "Expected ';' at end of export statement"); +} + //precedence functions static void expressionStmt(Parser* parser, Node** nodeHandle) { //BUGFIX: check for empty statements @@ -1248,6 +1292,18 @@ static void statement(Parser* parser, Node** nodeHandle) { return; } + //import + if (match(parser, TOKEN_IMPORT)) { + importStmt(parser, nodeHandle); + return; + } + + //export + if (match(parser, TOKEN_EXPORT)) { + exportStmt(parser, nodeHandle); + return; + } + //default expressionStmt(parser, nodeHandle); } diff --git a/test/test_interpreter.c b/test/test_interpreter.c index 89d0c8a..1a09fd1 100644 --- a/test/test_interpreter.c +++ b/test/test_interpreter.c @@ -162,13 +162,14 @@ int main() { { //run each file in ../scripts/test/ - int count = 14; + int count = 15; char* filenames[] = { "arithmetic.toy", "casting.toy", "comparisons.toy", "dot-and-matrix.toy", "functions.toy", + "imports-and-exports.toy", "jumps.toy", "jumps-in-functions.toy", "logicals.toy",