From db0f4ebb5e01701d4363fa0ae01d6017f17402c6 Mon Sep 17 00:00:00 2001 From: Thakee Nathees Date: Wed, 2 Jun 2021 15:03:29 +0530 Subject: [PATCH] controlflow bugs fixed --- src/compiler.c | 59 +++++++------- src/core.c | 33 +++++++- src/vm.c | 8 +- test/examples/brainfuck.pk | 116 ++++++++++++++++++++++++++++ test/lang/{if.pk => controlflow.pk} | 22 +++++- test/lang/import.pk | 4 +- test/run.py | 2 +- 7 files changed, 199 insertions(+), 45 deletions(-) create mode 100644 test/examples/brainfuck.pk rename test/lang/{if.pk => controlflow.pk} (71%) diff --git a/src/compiler.c b/src/compiler.c index 44f38b4..b0620fb 100644 --- a/src/compiler.c +++ b/src/compiler.c @@ -974,17 +974,17 @@ GrammarRule rules[] = { // Prefix Infix Infix Precedence /* TK_STAR */ { NULL, exprBinaryOp, PREC_FACTOR }, /* TK_FSLASH */ { NULL, exprBinaryOp, PREC_FACTOR }, /* TK_BSLASH */ NO_RULE, - /* TK_EQ */ NO_RULE, // exprAssignment, PREC_ASSIGNMENT + /* TK_EQ */ NO_RULE, /* TK_GT */ { NULL, exprBinaryOp, PREC_COMPARISION }, /* TK_LT */ { NULL, exprBinaryOp, PREC_COMPARISION }, /* TK_EQEQ */ { NULL, exprBinaryOp, PREC_EQUALITY }, /* TK_NOTEQ */ { NULL, exprBinaryOp, PREC_EQUALITY }, /* TK_GTEQ */ { NULL, exprBinaryOp, PREC_COMPARISION }, /* TK_LTEQ */ { NULL, exprBinaryOp, PREC_COMPARISION }, - /* TK_PLUSEQ */ NO_RULE, // exprAssignment, PREC_ASSIGNMENT - /* TK_MINUSEQ */ NO_RULE, // exprAssignment, PREC_ASSIGNMENT - /* TK_STAREQ */ NO_RULE, // exprAssignment, PREC_ASSIGNMENT - /* TK_DIVEQ */ NO_RULE, // exprAssignment, PREC_ASSIGNMENT + /* TK_PLUSEQ */ NO_RULE, + /* TK_MINUSEQ */ NO_RULE, + /* TK_STAREQ */ NO_RULE, + /* TK_DIVEQ */ NO_RULE, /* TK_SRIGHT */ { NULL, exprBinaryOp, PREC_BITWISE_SHIFT }, /* TK_SLEFT */ { NULL, exprBinaryOp, PREC_BITWISE_SHIFT }, /* TK_MODULE */ NO_RULE, @@ -1554,17 +1554,17 @@ static void compilerEnterBlock(Compiler* compiler) { compiler->scope_depth++; } -// Pop all the locals at the [depth] or highter. -static void compilerPopLocals(Compiler* compiler, int depth) { +// Pop all the locals at the [depth] or highter. Returns the number of locals +// that were poppedl +static int compilerPopLocals(Compiler* compiler, int depth) { ASSERT(depth > (int)DEPTH_GLOBAL, "Cannot pop global variables."); int local = compiler->var_count - 1; while (local >= 0 && compiler->variables[local].depth >= depth) { emitOpcode(compiler, OP_POP); - compiler->var_count--; - compiler->stack_size--; local--; } + return (compiler->var_count - 1) - local; } // Exits a block. @@ -1572,7 +1572,9 @@ static void compilerExitBlock(Compiler* compiler) { ASSERT(compiler->scope_depth > (int)DEPTH_GLOBAL, "Cannot exit toplevel."); // Discard all the locals at the current scope. - compilerPopLocals(compiler, compiler->scope_depth); + int popped = compilerPopLocals(compiler, compiler->scope_depth); + compiler->var_count -= popped; + compiler->stack_size -= popped; compiler->scope_depth--; } @@ -1617,7 +1619,7 @@ static void emitConstant(Compiler* compiler, Var value) { // Update the jump offset. static void patchJump(Compiler* compiler, int addr_index) { - int offset = (int)_FN->opcodes.count - addr_index - 2; + int offset = (int)_FN->opcodes.count - (addr_index + 2 /*bytes index*/); ASSERT(offset < MAX_JUMP, "Too large address offset to jump to."); _FN->opcodes.data[addr_index] = (offset >> 8) & 0xff; @@ -1644,7 +1646,6 @@ typedef enum { BLOCK_FUNC, BLOCK_LOOP, BLOCK_IF, - BLOCK_ELIF, BLOCK_ELSE, } BlockType; @@ -1747,10 +1748,6 @@ static void compileBlockBody(Compiler* compiler, BlockType type) { consumeStartBlock(compiler, TK_THEN); skipNewLines(compiler); - } else if (type == BLOCK_ELIF) { - // Do nothing, because this will be parsed as a new if statement. - // and it's condition hasn't parsed yet. - } else if (type == BLOCK_ELSE) { skipNewLines(compiler); @@ -1764,11 +1761,9 @@ static void compileBlockBody(Compiler* compiler, BlockType type) { skipNewLines(compiler); } - bool if_body = (type == BLOCK_IF) || (type == BLOCK_ELIF); - TokenType next = peek(compiler); while (!(next == TK_END || next == TK_EOF || ( - if_body && (next == TK_ELSE || next == TK_ELIF)))) { + (type == BLOCK_IF) && (next == TK_ELSE || next == TK_ELIF)))) { compileStatement(compiler); skipNewLines(compiler); @@ -2091,7 +2086,7 @@ static void compileExpression(Compiler* compiler) { parsePrecedence(compiler, PREC_LOWEST); } -static void compileIfStatement(Compiler* compiler) { +static void compileIfStatement(Compiler* compiler, bool elif) { skipNewLines(compiler); compileExpression(compiler); //< Condition. @@ -2100,23 +2095,19 @@ static void compileIfStatement(Compiler* compiler) { compileBlockBody(compiler, BLOCK_IF); - // Elif statement's don't consume 'end' after they end since it's treated as - // else and if they require 2 'end' statements. But we're omitting the 'end' - // for the 'else' since it'll consumed by the 'if'. - bool elif = false; - - if (peek(compiler) == TK_ELIF) { - elif = true; - // Override the elif to if so that it'll be parsed as a new if statement - // and that's why we're not consuming it here. - compiler->current.type = TK_IF; + if (match(compiler, TK_ELIF)) { // Jump pass else. emitOpcode(compiler, OP_JUMP); int exit_jump = emitShort(compiler, 0xffff); //< Will be patched. + // if (false) jump here. patchJump(compiler, ifpatch); - compileBlockBody(compiler, BLOCK_ELIF); + + compilerEnterBlock(compiler); + compileIfStatement(compiler, true); + compilerExitBlock(compiler); + patchJump(compiler, exit_jump); } else if (match(compiler, TK_ELSE)) { @@ -2133,6 +2124,8 @@ static void compileIfStatement(Compiler* compiler) { patchJump(compiler, ifpatch); } + // elif will not consume the 'end' keyword as it'll be leaved to be consumed + // by it's 'if'. if (!elif) { skipNewLines(compiler); consume(compiler, TK_END, "Expected 'end' after statement end."); @@ -2240,7 +2233,7 @@ static void compileStatement(Compiler* compiler) { compilerPopLocals(compiler, compiler->loop->depth + 1); emitOpcode(compiler, OP_JUMP); - int patch = emitByte(compiler, 0xffff); //< Will be patched. + int patch = emitShort(compiler, 0xffff); //< Will be patched. compiler->loop->patches[compiler->loop->patch_count++] = patch; } else if (match(compiler, TK_CONTINUE)) { @@ -2271,7 +2264,7 @@ static void compileStatement(Compiler* compiler) { emitOpcode(compiler, OP_RETURN); } } else if (match(compiler, TK_IF)) { - compileIfStatement(compiler); + compileIfStatement(compiler, false); } else if (match(compiler, TK_WHILE)) { compileWhileStatement(compiler); diff --git a/src/core.c b/src/core.c index 9b045cc..0d2c707 100644 --- a/src/core.c +++ b/src/core.c @@ -215,7 +215,7 @@ static inline bool validateNumeric(PKVM* vm, Var var, double* value, } // Check if [var] is integer. If not set error and return false. -static inline bool validateIngeger(PKVM* vm, Var var, int32_t* value, +static inline bool validateInteger(PKVM* vm, Var var, int32_t* value, const char* name) { double number; if (isNumeric(var, &number)) { @@ -423,6 +423,29 @@ void coreStrStrip(PKVM* vm) { RET(VAR_OBJ(&newStringLength(vm, start, (uint32_t)(end - start + 1))->_super)); } +// Returns the ASCII string value of the integer argument. +void coreStrChr(PKVM* vm) { + int32_t num; + if (!validateInteger(vm, ARG1, &num, "Argument 1")); + + char c = (char)num; + RET(VAR_OBJ(&newStringLength(vm, &c, 1)->_super)); +} + +// Returns integer value of the given ASCII character. +void coreStrOrd(PKVM* vm) { + String* c; + if (!validateArgString(vm, 1, &c)); + if (c->length != 1) { + vm->fiber->error = newString(vm, "Expected a string of length 1."); + RET(VAR_NULL); + } else { + RET(VAR_NUM((double)c->data[0])); + } +} + + + // List functions. // --------------- void coreListAppend(PKVM* vm) { @@ -634,6 +657,8 @@ void initializeCore(PKVM* vm) { INITALIZE_BUILTIN_FN("str_lower", coreStrLower, 1); INITALIZE_BUILTIN_FN("str_upper", coreStrUpper, 1); INITALIZE_BUILTIN_FN("str_strip", coreStrStrip, 1); + INITALIZE_BUILTIN_FN("str_chr", coreStrChr, 1); + INITALIZE_BUILTIN_FN("str_ord", coreStrOrd, 1); // List functions. INITALIZE_BUILTIN_FN("list_append", coreListAppend, 2); @@ -991,7 +1016,7 @@ Var varGetSubscript(PKVM* vm, Var on, Var key) { { int32_t index; String* str = ((String*)obj); - if (!validateIngeger(vm, key, &index, "List index")) { + if (!validateInteger(vm, key, &index, "List index")) { return VAR_NULL; } if (!validateIndex(vm, index, str->length, "String")) { @@ -1005,7 +1030,7 @@ Var varGetSubscript(PKVM* vm, Var on, Var key) { { int32_t index; VarBuffer* elems = &((List*)obj)->elements; - if (!validateIngeger(vm, key, &index, "List index")) { + if (!validateInteger(vm, key, &index, "List index")) { return VAR_NULL; } if (!validateIndex(vm, index, (int)elems->count, "List")) { @@ -1063,7 +1088,7 @@ void varsetSubscript(PKVM* vm, Var on, Var key, Var value) { { int32_t index; VarBuffer* elems = &((List*)obj)->elements; - if (!validateIngeger(vm, key, &index, "List index")) return; + if (!validateInteger(vm, key, &index, "List index")) return; if (!validateIndex(vm, index, (int)elems->count, "List")) return; elems->data[index] = value; return; diff --git a/src/vm.c b/src/vm.c index a291b2f..01e34b9 100644 --- a/src/vm.c +++ b/src/vm.c @@ -9,9 +9,10 @@ #include "core.h" #include "utils.h" -#if DEBUG_DUMP_CALL_STACK - #include "debug.h" //< Wrap around debug macro. -#endif +//#if DEBUG_DUMP_CALL_STACK +// #include "debug.h" +//#endif +#include "debug.h" // Evaluvated to true if a runtime error set on the current fiber. #define HAS_ERROR() (vm->fiber->error != NULL) @@ -1062,6 +1063,7 @@ PkInterpretResult vmRunScript(PKVM* vm, Script* _script) { POP(); POP(); // r, l PUSH(value); + CHECK_ERROR(); DISPATCH(); } diff --git a/test/examples/brainfuck.pk b/test/examples/brainfuck.pk new file mode 100644 index 0000000..6f65a99 --- /dev/null +++ b/test/examples/brainfuck.pk @@ -0,0 +1,116 @@ +from lang import write + +############################################################################### +## BRAINFUCK IMPLEMENTATION IN POCKETLANG ## +############################################################################### + +## Reference: https://en.wikipedia.org/wiki/Brainfuck + +## Note that this interpreter implementation is just to test pocketlang and is +## not an efficient one. This could be optimized by evaluvating the expressions +## at "compile time" (AOT) to avoid re-evaluvating those expressions at runtime +## and also we can pre compile the loop jump offsets. + +## Source: https://en.wikipedia.org/wiki/Brainfuck +hello_world = "++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-] + >>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++." + +## Source: https://github.com/fabianishere/brainfuck/blob/master/examples/asciiart/triangle.bf +## Madeby: Nyyrikki(2002) +triangle = ">++++[<++++++++>-]>++++++++[>++++<-]>>++>>>+>>>+<<<<<<<<<<[-[->+<]> + [-<+>>>.<<]>>>[[->++++++++[>++++<-]>.<<[->+<]+>[->++++++++++<<+>]>. + [-]>]]+<<<[-[->+<]+>[-<+>>>-[->+<]++>[-<->]<<<]<<<<]++++++++++.+++. + [-]<]+++++" + +## Source: https://github.com/fabianishere/brainfuck/blob/master/examples/math/fib.bf +fibonacci = ">++++++++++>+>+[ + [+++++[>++++++++<-]>.<++++++[>--------<-]+<<<]>.>>[ + [-]<[>+<-]>>[<<+>+>-]<[>+<-[>+<-[>+<-[>+<-[>+<-[>+<- + [>+<-[>+<-[>+<-[>[-]>+>+<<<-[>+<-]]]]]]]]]]]+>>> + ]<<< + ]" + +execute(hello_world) +execute(triangle) +execute(fibonacci) + +############################################################################### +## INTERNAL ## +############################################################################### + +def execute(expr) + ptr = 0 ## Data pointer. + mem = [0] ## Data / Memory + + i = 0 ## Instruction pointer + while true + c = expr[i] ## Current char. + + ## Increment the data pointer (to point to the next cell to the right). + if c == '>' + ptr += 1 + if ptr >= mem.length then list_append(mem, 0) end + + ## Decrement the data pointer (to point to the next cell to the left). + elif c == '<' + ptr -= 1 + if ptr < 0 then assert(false, "ip < 0") end + + ## Increment (increase by one) the byte at the data pointer. + elif c == '+' + if mem[ptr] == 255 then mem[ptr] = 0 + else mem[ptr] += 1 end + + ## Decrement (decrease by one) the byte at the data pointer. + elif c == '-' + if mem[ptr] == 0 then mem[ptr] = 255 + else mem[ptr] -= 1 end + + ## output the byte at the data pointer. + elif c == '.' + write(str_chr(mem[ptr])) + + elif c == ',' + assert(false, "Currently input isn't supported in pocketlang.") + + ## if the byte at the data pointer is zero, then instead of moving the + ## instruction pointer forward to the next command, jump it forward to + ## the command after the matching ] command. + elif c == '[' and mem[ptr] == 0 + open = 0 + while true + i += 1 + if expr[i] == ']' and open == 0 then break end + + if expr[i] == '[' then open += 1 + elif expr[i] == ']' then open -= 1 + end assert(open >= 0) + end + + ## if the byte at the data pointer is nonzero, then instead of moving the + ## instruction pointer forward to the next command, jump it back to the + ## command after the matching [ command + elif c == ']' and mem[ptr] != 0 + open = 0 + while true + i -= 1 + if expr[i] == '[' and open == 0 then break end + + if expr[i] == ']' then open -= 1 + elif expr[i] == '[' then open += 1 + end assert(open <= 0) + end + + else + ## Any other characters are ignored in brainfuck interpreter. + end + + ## Increment the instruction pointer by one. + ## If we reached the end terminate. + i += 1 + if i == expr.length then break end + + end + +end + diff --git a/test/lang/if.pk b/test/lang/controlflow.pk similarity index 71% rename from test/lang/if.pk rename to test/lang/controlflow.pk index 32e7d01..2c913d0 100644 --- a/test/lang/if.pk +++ b/test/lang/controlflow.pk @@ -1,5 +1,5 @@ -## If statement tests. +## If statements. variable = null ## Will be changed by the control flow. unreachable = func assert(false, 'Unreachable') end @@ -29,3 +29,23 @@ elif true end assert(variable == 123, 'Nested if statement failed.') +## While statements + +while true + variable = 1212 + if true then break end +end +assert(variable == 1212) + + +while true + variable = 22 + if true then elif false then end + if false + elif true + variable += 2300 + end + variable += 1 + break +end +assert(variable == 2323) diff --git a/test/lang/import.pk b/test/lang/import.pk index 4b38fff..089ec83 100644 --- a/test/lang/import.pk +++ b/test/lang/import.pk @@ -5,14 +5,12 @@ import lang, path import lang as o, path as p from lang import write from lang import clock as c -from path import abspath, curdir -from path import abspath as ap, curdir as cd from lang import * from path import * import "basics.pk" ## will import all -import "if.pk" as if_test +import "controlflow.pk" as if_test from "functions.pk" import fn1, fn2 as f2, fn3 ## If it has a module name it'll bind to that name. diff --git a/test/run.py b/test/run.py index 65bebcb..530d8ca 100644 --- a/test/run.py +++ b/test/run.py @@ -8,7 +8,7 @@ INDENTATION = ' | ' test_files = [ "lang/basics.pk", "lang/functions.pk", - "lang/if.pk", + "lang/controlflow.pk", "examples/fib.pk", "examples/prime.pk", ]