From aac9bb37f75d95ff3276e76ccbabb32e60ad0803 Mon Sep 17 00:00:00 2001 From: Thakee Nathees Date: Fri, 27 May 2022 17:14:23 +0530 Subject: [PATCH] invalid definition inside expression fixed --- scripts/run_tests.py | 1 + src/core/compiler.c | 60 +++++++++++++++++++++++++++++++++++++ src/core/core.c | 6 ++-- src/core/debug.c | 1 + src/core/opcodes.h | 3 ++ src/core/vm.c | 6 ++++ src/libs/std_io.c | 1 + tests/lang/controlflow.pk | 6 ++++ tests/modules/io.File.pk | 26 ++++++++++++++++ tests/modules/test_file.txt | 4 +++ 10 files changed, 111 insertions(+), 3 deletions(-) create mode 100644 tests/modules/io.File.pk create mode 100644 tests/modules/test_file.txt diff --git a/scripts/run_tests.py b/scripts/run_tests.py index b9d79d1..8904858 100644 --- a/scripts/run_tests.py +++ b/scripts/run_tests.py @@ -33,6 +33,7 @@ TEST_SUITE = { "Modules Test" : ( "modules/dummy.pk", "modules/math.pk", + "modules/io.File.pk", ), "Random Scripts" : ( diff --git a/src/core/compiler.c b/src/core/compiler.c index 4420655..07cfe5a 100644 --- a/src/core/compiler.c +++ b/src/core/compiler.c @@ -479,6 +479,18 @@ struct Compiler { // "r-value". bool l_value; + // We can do a new assignment inside an expression however we shouldn't + // define a new one, since in pocketlang both assignment and definition + // are syntactically the same, we use [can_define] "context" to prevent + // such assignments. + bool can_define; + + // This value will set to true for parsing a temproary expression + // (ie. if ...) and the value of the expression will be popped out of + // the stack onece we're done, if it's a defenition, that value should be + // duplicated on the stack to prevent it. + bool expr_temp; + // This value will be true after parsing a call expression, for every other // Expressions it'll be false. This is **ONLY** to be used when compiling a // return statement to check if the last parsed expression is a call to @@ -551,6 +563,8 @@ static void parserInit(Parser* parser, PKVM* vm, Compiler* compiler, static void compilerInit(Compiler* compiler, PKVM* vm, const char* source, Module* module, const CompileOptions* options) { + memset(compiler, 0, sizeof(Compiler)); + compiler->next_compiler = NULL; compiler->module = module; @@ -561,6 +575,7 @@ static void compilerInit(Compiler* compiler, PKVM* vm, const char* source, compiler->loop = NULL; compiler->func = NULL; + compiler->can_define = true; compiler->new_local = false; compiler->is_last_call = false; @@ -1880,7 +1895,14 @@ static void exprInterpolation(Compiler* compiler) { } static void exprFunction(Compiler* compiler) { + bool can_define = compiler->can_define; + bool expr_temp = compiler->expr_temp; + + compiler->can_define = true; + compiler->expr_temp = false; compileFunction(compiler, FUNC_LITERAL); + compiler->can_define = can_define; + compiler->expr_temp = expr_temp; } static void exprName(Compiler* compiler) { @@ -1926,10 +1948,18 @@ static void exprName(Compiler* compiler) { if (name_type == NAME_LOCAL_VAR) { new_local = true; } + + if (!compiler->can_define) { + semanticError(compiler, tkname, + "Variable definition isn't allowed here."); + } } // Compile the assigned value. + bool can_define = compiler->can_define; + compiler->can_define = false; compileExpression(compiler); + compiler->can_define = can_define; } else { // name += / -= / *= ... = (expr); @@ -1963,6 +1993,11 @@ static void exprName(Compiler* compiler) { // If the compiler has errors, we cannot and don't have to assert. ASSERT(compiler->parser.has_errors || (compiler->func->stack_size - 1) == index, OOPS); + + if (compiler->expr_temp) { + emitOpcode(compiler, OP_DUP); + } + } else { // The assigned value or the result of the operator will be at the top of // the stack by now. Store it. @@ -2281,9 +2316,18 @@ static void parsePrecedence(Compiler* compiler, Precedence precedence) { // reset once it done. bool l_value = compiler->l_value; + // Inside an expression no new difinition is allowed. We make a "backup" + // here to prevent such and reset it once we're done. + bool can_define = compiler->can_define; + if (prefix != exprName) compiler->can_define = false; + compiler->l_value = precedence <= PREC_LOWEST; prefix(compiler); + // Prefix expression can be either allow or not allow a definition however + // an infix expression can never be a definition. + compiler->can_define = false; + // The above expression cannot be a call '(', since call is an infix // operator. But could be true (ex: x = f()). we set is_last_call to false // here and if the next infix operator is call this will be set to true @@ -2304,6 +2348,7 @@ static void parsePrecedence(Compiler* compiler, Precedence precedence) { } compiler->l_value = l_value; + compiler->can_define = can_define; } /*****************************************************************************/ @@ -3041,7 +3086,12 @@ static void compileExpression(Compiler* compiler) { static void compileIfStatement(Compiler* compiler, bool elif) { skipNewLines(compiler); + + bool expr_temp = compiler->expr_temp; + compiler->expr_temp = true; compileExpression(compiler); //< Condition. + compiler->expr_temp = expr_temp; + emitOpcode(compiler, OP_JUMP_IF_NOT); int ifpatch = emitShort(compiler, 0xffff); //< Will be patched. @@ -3090,7 +3140,11 @@ static void compileWhileStatement(Compiler* compiler) { loop.depth = compiler->scope_depth; compiler->loop = &loop; + bool expr_temp = compiler->expr_temp; + compiler->expr_temp = true; compileExpression(compiler); //< Condition. + compiler->expr_temp = expr_temp; + emitOpcode(compiler, OP_JUMP_IF_NOT); int whilepatch = emitShort(compiler, 0xffff); //< Will be patched. @@ -3122,7 +3176,10 @@ static void compileForStatement(Compiler* compiler) { // Compile and store sequence. compilerAddVariable(compiler, "@Sequence", 9, iter_line); // Sequence + bool expr_temp = compiler->expr_temp; + compiler->expr_temp = true; compileExpression(compiler); + compiler->expr_temp = expr_temp; // Add iterator to locals. It's an increasing integer indicating that the // current loop is nth starting from 0. @@ -3233,7 +3290,10 @@ static void compileStatement(Compiler* compiler) { "Cannor 'return' a value from constructor."); } + bool can_define = compiler->can_define; + compiler->can_define = false; compileExpression(compiler); //< Return value is at stack top. + compiler->can_define = can_define; // If the last expression parsed with compileExpression() is a call // is_last_call would be true by now. diff --git a/src/core/core.c b/src/core/core.c index bf7896b..71b2f6d 100644 --- a/src/core/core.c +++ b/src/core/core.c @@ -737,9 +737,9 @@ DEF(_objTypeName, } DEF(_objRepr, - "Object.repr() -> String\n" + "Object._repr() -> String\n" "Returns the repr string of the object.") { - RET(VAR_OBJ(varToString(vm, SELF, true))); + RET(VAR_OBJ(toRepr(vm, SELF))); } DEF(_numberTimes, @@ -1145,7 +1145,7 @@ static void initializePrimitiveClasses(PKVM* vm) { // TODO: write docs. ADD_METHOD(PK_OBJECT, "typename", _objTypeName, 0); - ADD_METHOD(PK_OBJECT, "repr", _objRepr, 0); + ADD_METHOD(PK_OBJECT, "_repr", _objRepr, 0); ADD_METHOD(PK_NUMBER, "times", _numberTimes, 1); ADD_METHOD(PK_NUMBER, "isint", _numberIsint, 0); diff --git a/src/core/debug.c b/src/core/debug.c index fbf927a..2e39e91 100644 --- a/src/core/debug.c +++ b/src/core/debug.c @@ -374,6 +374,7 @@ void dumpFunctionCode(PKVM* vm, Function* func) { case OP_PUSH_TRUE: case OP_PUSH_FALSE: case OP_SWAP: + case OP_DUP: NO_ARGS(); break; diff --git a/src/core/opcodes.h b/src/core/opcodes.h index e0cb890..41e7208 100644 --- a/src/core/opcodes.h +++ b/src/core/opcodes.h @@ -33,6 +33,9 @@ OPCODE(PUSH_FALSE, 0, 1) // Swap the top 2 stack values. OPCODE(SWAP, 0, 0) +// Duplicate the stack top value. +OPCODE(DUP, 0, 1) + // Push a new list to construct from literal. // param: 2 bytes list size (default is 0). OPCODE(PUSH_LIST, 2, 1) diff --git a/src/core/vm.c b/src/core/vm.c index b22255c..ee13975 100644 --- a/src/core/vm.c +++ b/src/core/vm.c @@ -828,6 +828,12 @@ L_vm_main_loop: DISPATCH(); } + OPCODE(DUP): + { + PUSH(*(fiber->sp - 1)); + DISPATCH(); + } + OPCODE(PUSH_LIST): { List* list = newList(vm, (uint32_t)READ_SHORT()); diff --git a/src/libs/std_io.c b/src/libs/std_io.c index 36db0a0..dbf89da 100644 --- a/src/libs/std_io.c +++ b/src/libs/std_io.c @@ -407,6 +407,7 @@ DEF(_fileTell, "") { // from io import File // return File().open(path, mode) DEF(_open, NULL /* == _fileOpen */) { + pkReserveSlots(vm, 3); // slots[1] = path // slots[2] = mode diff --git a/tests/lang/controlflow.pk b/tests/lang/controlflow.pk index cdf5a40..2115b96 100644 --- a/tests/lang/controlflow.pk +++ b/tests/lang/controlflow.pk @@ -71,5 +71,11 @@ end assert(val == 'bar') +def test_in_local() + if x = fn y = false; return not y end () + assert(x == true) + end +end + # If we got here, that means all test were passed. print('All TESTS PASSED') diff --git a/tests/modules/io.File.pk b/tests/modules/io.File.pk new file mode 100644 index 0000000..8814509 --- /dev/null +++ b/tests/modules/io.File.pk @@ -0,0 +1,26 @@ + +from io import File +from path import join, dirname + +FILE_PATH = join(dirname(__file__), 'test_file.txt') + +def read_file() + f = open(FILE_PATH) + + assert(f is File) + + LINES = [ + 'line1 : foo\n', + 'line2 : bar\n', + 'line3 : baz\n', + 'line4 : qux\n', + ] + + while line = f.getline() + assert(line in LINES) + end + + f.close() +end + +read_file() diff --git a/tests/modules/test_file.txt b/tests/modules/test_file.txt new file mode 100644 index 0000000..6cc22f0 --- /dev/null +++ b/tests/modules/test_file.txt @@ -0,0 +1,4 @@ +line1 : foo +line2 : bar +line3 : baz +line4 : qux