diff --git a/docs/TODO.txt b/docs/TODO.txt index c63f697..1f6b962 100644 --- a/docs/TODO.txt +++ b/docs/TODO.txt @@ -31,17 +31,18 @@ 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 +DONE: A way to check the type of a variable (typeOf keyword) TODO: slice and dot notation around the builtin _index function TODO: ternary operator TODO: Nullish types? -TODO: A way to check the type of a variable (typeOf keyword) -TODO: a = b = c = 1; ? +TODO: hooks on the external libraries, triggered on import TODO: standard library TODO: external script runner library TODO: document how it all works - book? TODO: maximum recursion/function depth +NOPE: a = b = c = 1; NOPE: functions return a set number of values \ No newline at end of file diff --git a/docs/spec.md b/docs/spec.md index 601cf5f..4534e15 100644 --- a/docs/spec.md +++ b/docs/spec.md @@ -49,6 +49,7 @@ The following list of keywords cannot be used as names, due to their significanc * string * true * type +* typeof * var * while @@ -163,11 +164,7 @@ var bar = "hello world"; var buzz; ``` -Variables can be used in place of values at any time, and can be altered and re-assigned. Multiple variables (which have been previously declared) can be assigned to the same value: - -``` -a = b = c = 1; -``` +Variables can be used in place of values at any time, and can be altered and re-assigned. ## If-Else diff --git a/scripts/test/imports-and-exports.toy b/scripts/test/imports-and-exports.toy index 05bd307..0225dc1 100644 --- a/scripts/test/imports-and-exports.toy +++ b/scripts/test/imports-and-exports.toy @@ -32,6 +32,17 @@ assert func() == 69, "import/export of functions failed"; } -//TODO: test that variables retain their types with the typeOf keyword +//test that variables retain their types with the typeof keyword +{ + var t: type = int; + + export t; +} + +{ + import t; + + assert typeof t == type type, "type retention failed"; +} print "All good"; diff --git a/source/interpreter.c b/source/interpreter.c index 3d83447..371dccd 100644 --- a/source/interpreter.c +++ b/source/interpreter.c @@ -71,40 +71,6 @@ bool parseIdentifierToValue(Interpreter* interpreter, Literal* literalPtr) { return true; } -void initInterpreter(Interpreter* interpreter) { - //NOTE: separate initialization for exports - interpreter->exports = ALLOCATE(LiteralDictionary, 1); - initLiteralDictionary(interpreter->exports); - interpreter->exportTypes = ALLOCATE(LiteralDictionary, 1); - initLiteralDictionary(interpreter->exportTypes); - - //set up the output streams - setInterpreterPrint(interpreter, printWrapper); - setInterpreterAssert(interpreter, assertWrapper); - setInterpreterError(interpreter, errorWrapper); -} - -void freeInterpreter(Interpreter* interpreter) { - //BUGFIX: handle scopes/types in the exports - for (int i = 0; i < interpreter->exports->capacity; i++) { - if (IS_FUNCTION(interpreter->exports->entries[i].key)) { - popScope(AS_FUNCTION(interpreter->exports->entries[i].key).scope); - AS_FUNCTION(interpreter->exports->entries[i].key).scope = NULL; - } - if (IS_FUNCTION(interpreter->exports->entries[i].value)) { - popScope(AS_FUNCTION(interpreter->exports->entries[i].value).scope); - AS_FUNCTION(interpreter->exports->entries[i].value).scope = NULL; - } - } - - freeLiteralDictionary(interpreter->exports); - FREE(LiteralDictionary, interpreter->exports); - interpreter->exports = NULL; - freeLiteralDictionary(interpreter->exportTypes); - FREE(LiteralDictionary, interpreter->exportTypes); - interpreter->exportTypes = NULL; -} - //utilities for the host program void setInterpreterPrint(Interpreter* interpreter, PrintFn printOutput) { interpreter->printOutput = printOutput; @@ -722,6 +688,25 @@ static bool execValCast(Interpreter* interpreter) { return true; } +static bool execTypeOf(Interpreter* interpreter) { + Literal rhs = popLiteralArray(&interpreter->stack); + Literal type = TO_NULL_LITERAL; + + if (IS_IDENTIFIER(rhs)) { + type = getScopeType(interpreter->scope, rhs); + } + else { + type = TO_TYPE_LITERAL(rhs.type, false); + } + + pushLiteralArray(&interpreter->stack, type); + + freeLiteral(rhs); + freeLiteral(type); + + return true; +} + static bool execCompareEqual(Interpreter* interpreter, bool invert) { Literal rhs = popLiteralArray(&interpreter->stack); Literal lhs = popLiteralArray(&interpreter->stack); @@ -1469,6 +1454,12 @@ static void execInterpreter(Interpreter* interpreter) { } break; + case OP_TYPE_OF: + if (!execTypeOf(interpreter)) { + return; + } + break; + case OP_COMPARE_EQUAL: if (!execCompareEqual(interpreter, false)) { return; @@ -1823,16 +1814,26 @@ static void readInterpreterSections(Interpreter* interpreter) { consumeByte(interpreter, OP_SECTION_END, interpreter->bytecode, &interpreter->count); //terminate the function section } -void runInterpreter(Interpreter* interpreter, unsigned char* bytecode, int length) { - //check for export zone - if (!interpreter->exports || !interpreter->exportTypes) { - interpreter->errorOutput("Interpreter has no export region\n"); - return; - } +//exposed functions +void initInterpreter(Interpreter* interpreter) { + //NOTE: separate initialization for exports + interpreter->exports = ALLOCATE(LiteralDictionary, 1); + initLiteralDictionary(interpreter->exports); + interpreter->exportTypes = ALLOCATE(LiteralDictionary, 1); + initLiteralDictionary(interpreter->exportTypes); + //set up the output streams + setInterpreterPrint(interpreter, printWrapper); + setInterpreterAssert(interpreter, assertWrapper); + setInterpreterError(interpreter, errorWrapper); + + interpreter->scope = NULL; + resetInterpreter(interpreter); +} + +void runInterpreter(Interpreter* interpreter, unsigned char* bytecode, int length) { //initialize here instead of initInterpreter() initLiteralArray(&interpreter->literalCache); - interpreter->scope = pushScope(NULL); interpreter->bytecode = NULL; interpreter->length = 0; interpreter->count = 0; @@ -1840,14 +1841,6 @@ void runInterpreter(Interpreter* interpreter, unsigned char* bytecode, int lengt initLiteralArray(&interpreter->stack); - //globally available functions - injectNativeFn(interpreter, "_set", _set); - injectNativeFn(interpreter, "_get", _get); - injectNativeFn(interpreter, "_push", _push); - injectNativeFn(interpreter, "_pop", _pop); - injectNativeFn(interpreter, "_length", _length); - injectNativeFn(interpreter, "_clear", _clear); - interpreter->panic = false; //prep the bytecode @@ -1902,15 +1895,54 @@ void runInterpreter(Interpreter* interpreter, unsigned char* bytecode, int lengt freeLiteral(lit); } - //free the bytecode + //free the bytecode immediately after use FREE_ARRAY(unsigned char, interpreter->bytecode, interpreter->length); //free the associated data freeLiteralArray(&interpreter->literalCache); freeLiteralArray(&interpreter->stack); +} +void resetInterpreter(Interpreter* interpreter) { //free the interpreter scope while(interpreter->scope != NULL) { interpreter->scope = popScope(interpreter->scope); } + + //prep the scope + interpreter->scope = pushScope(NULL); + + //globally available functions + injectNativeFn(interpreter, "_set", _set); + injectNativeFn(interpreter, "_get", _get); + injectNativeFn(interpreter, "_push", _push); + injectNativeFn(interpreter, "_pop", _pop); + injectNativeFn(interpreter, "_length", _length); + injectNativeFn(interpreter, "_clear", _clear); } + +void freeInterpreter(Interpreter* interpreter) { + //free the interpreter scope + while(interpreter->scope != NULL) { + interpreter->scope = popScope(interpreter->scope); + } + + //BUGFIX: handle scopes/types in the exports + for (int i = 0; i < interpreter->exports->capacity; i++) { + if (IS_FUNCTION(interpreter->exports->entries[i].key)) { + popScope(AS_FUNCTION(interpreter->exports->entries[i].key).scope); + AS_FUNCTION(interpreter->exports->entries[i].key).scope = NULL; + } + if (IS_FUNCTION(interpreter->exports->entries[i].value)) { + popScope(AS_FUNCTION(interpreter->exports->entries[i].value).scope); + AS_FUNCTION(interpreter->exports->entries[i].value).scope = NULL; + } + } + + freeLiteralDictionary(interpreter->exports); + FREE(LiteralDictionary, interpreter->exports); + interpreter->exports = NULL; + freeLiteralDictionary(interpreter->exportTypes); + FREE(LiteralDictionary, interpreter->exportTypes); + interpreter->exportTypes = NULL; +} \ No newline at end of file diff --git a/source/interpreter.h b/source/interpreter.h index 2552eee..708321c 100644 --- a/source/interpreter.h +++ b/source/interpreter.h @@ -41,6 +41,7 @@ void setInterpreterAssert(Interpreter* interpreter, PrintFn assertOutput); void setInterpreterError(Interpreter* interpreter, PrintFn errorOutput); //main access -void initInterpreter(Interpreter* interpreter); -void runInterpreter(Interpreter* interpreter, unsigned char* bytecode, int length); -void freeInterpreter(Interpreter* interpreter); +void initInterpreter(Interpreter* interpreter); //start of program +void runInterpreter(Interpreter* interpreter, unsigned char* bytecode, int length); //run the code +void resetInterpreter(Interpreter* interpreter); //use this to reset the interpreter's environment between runs +void freeInterpreter(Interpreter* interpreter); //end of program diff --git a/source/keyword_types.c b/source/keyword_types.c index 7275ba8..07bab6e 100644 --- a/source/keyword_types.c +++ b/source/keyword_types.c @@ -33,6 +33,7 @@ KeywordType keywordTypes[] = { {TOKEN_PRINT, "print"}, {TOKEN_RETURN, "return"}, {TOKEN_TYPE, "type"}, + {TOKEN_TYPEOF, "typeof"}, {TOKEN_VAR, "var"}, {TOKEN_WHILE, "while"}, diff --git a/source/literal.c b/source/literal.c index 52edc04..36afc85 100644 --- a/source/literal.c +++ b/source/literal.c @@ -624,7 +624,10 @@ void printLiteralCustom(Literal literal, void (printFn)(const char*)) { case LITERAL_FUNCTION: printToBuffer("function"); - //TODO: how to print a function + break; + + case LITERAL_FUNCTION_NATIVE: + printToBuffer("native"); break; case LITERAL_IDENTIFIER: diff --git a/source/opcodes.h b/source/opcodes.h index fe5f638..b2c6b9f 100644 --- a/source/opcodes.h +++ b/source/opcodes.h @@ -43,6 +43,7 @@ typedef enum Opcode { OP_VAR_MODULO_ASSIGN, OP_TYPE_CAST, //temporarily change a type of an atomic value + OP_TYPE_OF, //get the type of a variable OP_IMPORT, OP_EXPORT, diff --git a/source/parser.c b/source/parser.c index d9ed360..a8262ca 100644 --- a/source/parser.c +++ b/source/parser.c @@ -136,6 +136,13 @@ static Opcode forceType(Parser* parser, Node** nodeHandle) { return OP_EOF; } +static Opcode typeOf(Parser* parser, Node** nodeHandle) { + Node* rhs = NULL; + parsePrecedence(parser, &rhs, PREC_TERNARY); + emitNodeUnary(nodeHandle, OP_TYPE_OF, rhs); + return OP_EOF; +} + static Opcode compound(Parser* parser, Node** nodeHandle) { //read either an array or a dictionary into a literal node @@ -730,6 +737,7 @@ ParseRule parseRules[] = { //must match the token types {NULL, NULL, PREC_NONE},// TOKEN_PRINT, {NULL, NULL, PREC_NONE},// TOKEN_RETURN, {forceType, NULL, PREC_PRIMARY},// TOKEN_TYPE, + {typeOf, NULL, PREC_CALL},// TOKEN_TYPEOF, {NULL, NULL, PREC_NONE},// TOKEN_VAR, {NULL, NULL, PREC_NONE},// TOKEN_WHILE, @@ -1044,14 +1052,14 @@ static void blockStmt(Parser* parser, Node** nodeHandle) { //process the grammar rule for this line declaration(parser, &node); - //BUGFIX: statements no longer require an existing node - ((*nodeHandle)->block.nodes[(*nodeHandle)->block.count++]) = *node; - FREE(Node, node); //simply free the tmp node - // Ground floor: perfumery / Stationery and leather goods / Wigs and haberdashery / Kitchenware and food / Going up! if (parser->panic) { return; } + + //BUGFIX: statements no longer require an existing node + ((*nodeHandle)->block.nodes[(*nodeHandle)->block.count++]) = *node; + FREE(Node, node); //simply free the tmp node } } diff --git a/source/token_types.h b/source/token_types.h index c818478..a04b591 100644 --- a/source/token_types.h +++ b/source/token_types.h @@ -31,6 +31,7 @@ typedef enum TokenType { TOKEN_PRINT, TOKEN_RETURN, TOKEN_TYPE, + TOKEN_TYPEOF, TOKEN_VAR, TOKEN_WHILE, diff --git a/test/test_interpreter.c b/test/test_interpreter.c index e67b2b2..8b9fcb3 100644 --- a/test/test_interpreter.c +++ b/test/test_interpreter.c @@ -210,6 +210,9 @@ int main() { setInterpreterPrint(&interpreter, noPrintFn); runInterpreter(&interpreter, exportBinary, exportSize); //automatically frees the binary data + + resetInterpreter(&interpreter); + runInterpreter(&interpreter, importBinary, importSize); //automatically frees the binary data freeInterpreter(&interpreter);