diff --git a/build/SConstruct b/build/SConstruct index 2b5c5fe..72dc929 100644 --- a/build/SConstruct +++ b/build/SConstruct @@ -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, diff --git a/cli/all.c b/cli/all.c index ffafdd0..aca7f3b 100644 --- a/cli/all.c +++ b/cli/all.c @@ -30,5 +30,5 @@ /*****************************************************************************/ void registerModules(PKVM* vm) { - registerModulePath(vm); + registerModulePath(vm); } diff --git a/cli/modules/path.c b/cli/modules/path.c index 9457e90..65b2ff1 100644 --- a/cli/modules/path.c +++ b/cli/modules/path.c @@ -9,16 +9,13 @@ #include /* 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 #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 #include #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) diff --git a/src/common.h b/src/common.h index 4732a0f..91d57f7 100644 --- a/src/common.h +++ b/src/common.h @@ -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 */ /*****************************************************************************/ diff --git a/src/compiler.c b/src/compiler.c index 44f38b4..e22eed2 100644 --- a/src/compiler.c +++ b/src/compiler.c @@ -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); diff --git a/src/core.c b/src/core.c index 9b045cc..5430960 100644 --- a/src/core.c +++ b/src/core.c @@ -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; diff --git a/src/core.h b/src/core.h index 0f4d227..892fba9 100644 --- a/src/core.h +++ b/src/core.h @@ -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 diff --git a/src/vm.c b/src/vm.c index a291b2f..38a69cc 100644 --- a/src/vm.c +++ b/src/vm.c @@ -8,10 +8,7 @@ #include #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); } diff --git a/test/examples/brainfuck.pk b/test/examples/brainfuck.pk new file mode 100644 index 0000000..1980a7a --- /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", ]