Merge pull request #37 from ThakeeNathees/bug-fixes

controlflow bugs fixed
This commit is contained in:
Thakee Nathees 2021-06-02 17:44:17 +05:30
commit 2c1468f0e8
12 changed files with 224 additions and 75 deletions

View File

@ -18,7 +18,8 @@ def CONFIGURE_ENV(env):
## The executable to run with the current configuration.
env.RUN_TARGET = join(variant_dir, 'bin', binary_name)
env.SOURCE_DIRS = ('../src/', '../src/include/', '../cli/', '../cli/modules/')
## PocketLang source files
PK_SOURCES = Glob(join(variant_dir, 'src/*.c'))
@ -170,12 +171,11 @@ if env['vsproj']:
targets = [ env.RUN_TARGET ] * 4
variants = ["debug|Win32", "debug|x64", "release|Win32", "release|x64"]
source_dirs = ('../src/', '../src/include/', '../cli/', '../cli/modules/')
env.MSVSProject(
target = PROJECT_NAME + env['MSVSPROJECTSUFFIX'],
srcs = collect_source_files(source_dirs, ('.c', '.cpp', '.cc', '.cxx')),
incs = collect_source_files(source_dirs, ('.h', '.hpp')),
srcs = collect_source_files(env.SOURCE_DIRS, ('.c', '.cpp', '.cc', '.cxx')),
incs = collect_source_files(env.SOURCE_DIRS, ('.h', '.hpp')),
variant = variants,
runfile = targets,
buildtarget = targets,

View File

@ -30,5 +30,5 @@
/*****************************************************************************/
void registerModules(PKVM* vm) {
registerModulePath(vm);
registerModulePath(vm);
}

View File

@ -9,16 +9,13 @@
#include <stdio.h> /* defines FILENAME_MAX */
#include "../thirdparty/cwalk/cwalk.h"
#if defined(_MSC_VER)
#if defined(_WIN32) && (defined(_MSC_VER) || defined(__TINYC__))
#include "../thirdparty/dirent/dirent.h"
#else
#include <dirent.h>
#endif
// TODO: No error is handled below. I should check for path with size more than
// FILENAME_MAX.
#if defined(_WIN32) || defined(_WIN64) || defined(WINDOWS)
#if defined(_WIN32)
#include <windows.h>
#include <direct.h>
#define get_cwd _getcwd
@ -27,6 +24,10 @@
#define get_cwd getcwd
#endif
// TODO: No error is handled below. I should check for path with size more than
// FILENAME_MAX.
// TODO: this macros should be moved to a general place of in cli.
#define TOSTRING(x) #x
#define STRINGIFY(x) TOSTRING(x)

View File

@ -138,8 +138,14 @@
#define TOSTRING(x) #x
#define STRINGIFY(x) TOSTRING(x)
// Double to string buffer size.
#define STR_NUM_BUFF_SIZE (3 + DBL_MANT_DIG - DBL_MIN_EXP)
// Integer to string buffer size (INT_MAX, INT_MIN are 10 characters long, a
// negative sign, and a null byte at the end = 12 bytes).
#define STR_INT_BUFF_SIZE 12
/*****************************************************************************/
/* INTERNAL TYPE DEFINES */
/*****************************************************************************/

View File

@ -9,10 +9,7 @@
#include "buffers.h"
#include "utils.h"
#include "vm.h"
#if DEBUG_DUMP_COMPILED_CODE
#include "debug.h"
#endif
#include "debug.h"
// The maximum number of variables (or global if compiling top level script)
// to lookup from the compiling context. Also it's limited by it's opcode
@ -974,17 +971,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 +1551,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 +1569,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 +1616,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 +1643,6 @@ typedef enum {
BLOCK_FUNC,
BLOCK_LOOP,
BLOCK_IF,
BLOCK_ELIF,
BLOCK_ELSE,
} BlockType;
@ -1747,10 +1745,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 +1758,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 +2083,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 +2092,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 +2121,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 +2230,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 +2261,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);

View File

@ -49,7 +49,7 @@ void pkModuleAddFunction(PKVM* vm, PkHandle* module, const char* name,
// Argument count used in variadic functions.
#define ARGC ((int)(vm->fiber->sp - vm->fiber->ret) - 1)
// Set return value.
// Set return value and return.
#define RET(value) \
do { \
*(vm->fiber->ret) = value; \
@ -65,9 +65,7 @@ void pkModuleAddFunction(PKVM* vm, PkHandle* module, const char* name,
#define ERR_INVALID_ARG_TYPE(m_type) \
do { \
/* 12 chars is enought for a 4 byte integer string */ \
/* including the negative sign.*/ \
char buff[12]; \
char buff[STR_INT_BUFF_SIZE]; \
sprintf(buff, "%d", arg); \
vm->fiber->error = stringFormat(vm, "Expected a " m_type \
" at argument $.", buff); \
@ -131,7 +129,7 @@ bool pkGetArgValue(PKVM* vm, int arg, PkVarType type, PkVar* value) {
Var val = ARG(arg);
if (pkGetValueType((PkVar)&val) != type) {
char buff[12]; sprintf(buff, "%d", arg);
char buff[STR_NUM_BUFF_SIZE]; sprintf(buff, "%d", arg);
vm->fiber->error = stringFormat(vm,
"Expected a $ at argument $.", getPkVarTypeName(type), buff);
return false;
@ -215,11 +213,11 @@ 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)) {
double truncated = trunc(number);
double truncated = floor(number);
if (truncated == number) {
*value = (int32_t)(truncated);
return true;
@ -327,7 +325,6 @@ void coreAssert(PKVM* vm) {
return;
}
if (!toBool(ARG1)) {
String* msg = NULL;
@ -423,6 +420,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 +654,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 +1013,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 +1027,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 +1085,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;

View File

@ -26,7 +26,9 @@ const char* getBuiltinFunctionName(PKVM* vm, int index);
// otherwise returns NULL.
Script* getCoreLib(PKVM* vm, String* name);
// Operators //////////////////////////////////////////////////////////////////
/*****************************************************************************/
/* OPERATORS */
/*****************************************************************************/
Var varAdd(PKVM* vm, Var v1, Var v2);
Var varSubtract(PKVM* vm, Var v1, Var v2);
@ -43,5 +45,4 @@ void varSetAttrib(PKVM* vm, Var on, String* name, Var value);
Var varGetSubscript(PKVM* vm, Var on, Var key);
void varsetSubscript(PKVM* vm, Var on, Var key, Var value);
#endif // CORE_H

View File

@ -8,10 +8,7 @@
#include <math.h>
#include "core.h"
#include "utils.h"
#if DEBUG_DUMP_CALL_STACK
#include "debug.h" //< Wrap around debug macro.
#endif
#include "debug.h"
// Evaluvated to true if a runtime error set on the current fiber.
#define HAS_ERROR() (vm->fiber->error != NULL)
@ -548,7 +545,7 @@ PkInterpretResult vmRunScript(PKVM* vm, Script* _script) {
#if DEBUG_DUMP_CALL_STACK
#define DEBUG_CALL_STACK() \
do { \
system("cls"); \
system("cls"); /* FIXME */ \
dumpGlobalValues(vm); \
dumpStackFrame(vm); \
} while (false)
@ -747,11 +744,9 @@ PkInterpretResult vmRunScript(PKVM* vm, Script* _script) {
// -1 argument means multiple number of args.
if (fn->arity != -1 && fn->arity != argc) {
String* arg_str = toString(vm, VAR_NUM(fn->arity));
vmPushTempRef(vm, &arg_str->_super);
String* msg = stringFormat(vm, "Expected excatly @ argument(s).",
arg_str);
vmPopTempRef(vm); // arg_str.
char buff[STR_NUM_BUFF_SIZE]; sprintf(buff, "%d", fn->arity);
String* msg = stringFormat(vm, "Expected excatly $ argument(s).",
buff);
RUNTIME_ERROR(msg);
}

116
test/examples/brainfuck.pk Normal file
View File

@ -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

View File

@ -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)

View File

@ -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.

View File

@ -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",
]